лучше быстрая функция схемы?

53
5

Таким образом, для нахождения максимального элемента в списке требуется сложность времени O (n) (если в списке есть n элементов). Я попытался реализовать алгоритм, который выглядит быстрее.

(define (clever-max lst)
(define (odd-half a-list)
(cond ((null? a-list) (list))
((null? (cdr a-list))
(cons (car a-list) (list)))
(else
(cons (car a-list)
(odd-half (cdr (cdr a-list)))))))
(define (even-half a-list)
(if (null? a-list)
(list)
(odd-half (cdr a-list))))
(cond ((null? lst) (error "no elements in list!"))
((null? (cdr lst)) (car lst))
(else
(let ((l1 (even-half lst))
(l2 (odd-half lst)))
(max (clever-max l1) (clever-max l2))))))

Это быстрее? Что бы вы сказали, асимптотическая временная сложность (жесткая привязка)?

спросил(а) 2020-03-25T18:31:32+03:00 2 месяца назад
1
Решение
138

Учитывая список данных, о которых вы ничего не знаете, нет способа найти максимальный элемент без изучения каждого элемента и, следовательно, время O(n) потому что если вы его не проверите, вы можете его пропустить. Нет, ваш алгоритм не быстрее, чем O(n) это на самом деле O(n log n) поскольку вы в основном просто запускаете сортировку слияния.

Вот еще несколько данных о проблеме выбора

Я подумал об этом и понял, что должен, наверное, сделать что-то немного больше, чем просто утверждать это как факт. Поэтому я закодировал быстрый тест скорости. Теперь полное раскрытие, я не программист Схемы, так что это в Common Lisp, но я думаю, что я верно преобразовал ваш алгоритм.

;; Direct "iteration" method -- theoretical O(n)
(defun find-max-001 ( list )
(labels ((fm ( list cur )
(if (null list) cur
(let ((head (car list))
(rest (cdr list)))
(fm rest (if (> head cur) head cur))))))
(fm (cdr list) (car list))))

;; Your proposed method
(defun find-max-002 ( list )
(labels ((odd-half ( list )
(cond ((null list) list)
((null (cdr list)) (list (car list)))
(T (cons (car list) (odd-half (cddr list))))))
(even-half ( list )
(if (null list) list (odd-half (cdr list)))))
(cond ((null list) list)
((null (cdr list)) (car list))
(T (let ((l1 (even-half list))
(l2 (odd-half list)))
(max (find-max-002 l1) (find-max-002 l2)))))))

;; Simplistic speed test
(let ((list (loop for x from 0 to 10000 collect (random 10000))))
(progn
(print "Running find-max-001")
(time (find-max-001 list))
(print "Running find-max-002")
(time (find-max-002 list))))

Теперь вы можете спрашивать себя, почему я использую только 10000 для размера списка, потому что на самом деле это довольно мало для асимптотических вычислений. Истина заключается в том, что sbcl признает, что первая функция является хвостовой рекурсивной и, следовательно, абстрагирует ее в цикле, тогда как она не со второй, так что она настолько велика, насколько я мог бы получить, не убивая мой стек. Хотя, как видно из приведенных ниже результатов, это достаточно просто, чтобы проиллюстрировать суть.

"Running find-max-001"
Evaluation took:
0.000 seconds of real time
0.000000 seconds of total run time (0.000000 user, 0.000000 system)
100.00% CPU
128,862 processor cycles
0 bytes consed

"Running find-max-002"
Evaluation took:
0.012 seconds of real time
0.012001 seconds of total run time (0.012001 user, 0.000000 system)
[ Run times consist of 0.008 seconds GC time, and 0.005 seconds non-GC time. ]
100.00% CPU
27,260,311 processor cycles
2,138,112 bytes consed

Даже на этом уровне мы говорим о массовом замедлении. Он увеличил примерно до миллиона предметов до прямой проверки каждого элемента, как только метод замедляется до оценки 10k вашего алгоритма.

 (let ((x (loop for x from 0 to 1000000 collect (random 1000000))))
(time (find-max-001 x)))

Evaluation took:
0.007 seconds of real time
0.008000 seconds of total run time (0.008000 user, 0.000000 system)
114.29% CPU
16,817,949 processor cycles
0 bytes consed

Заключительные мысли и выводы

Поэтому следующий вопрос, который нужно задать, - это то, почему второй алгоритм действительно занимает гораздо больше времени. Не вдаваясь в подробности о устранении хвостовой рекурсии, есть несколько вещей, которые действительно выпрыгивают.

Первый из них - cons. Теперь да, cons - O(1) но это еще одна операция для прохождения системы. И это требует, чтобы система выделяла и освобождала память (приходится запускать сборщик мусора). Вторая вещь, которая действительно выпрыгивает, состоит в том, что вы в основном используете сортировку слияния, за исключением того, что вместо того, чтобы просто захватывать нижнюю и верхнюю половину списка, вы захватываете четные и нечетные узлы (это также займет больше времени, потому что вы должны итерации каждый время для создания списков). То, что у вас здесь, является алгоритмом O(n log n) в лучшем случае (помните, что он объединяет сортировку, которая действительно хороша для сортировки), но она несет много дополнительных накладных расходов.

ответил(а) 2020-03-25T18:48:01.732601+03:00 2 месяца назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема