SICP 2.64 порядок роста рекурсивной процедуры
Я занимаюсь самообучением SICP и с трудом нахожу порядок роста рекурсивных функций.
Следующая процедура list->tree преобразует упорядоченный список в сбалансированное дерево поиска:
(define (list->tree elements)
(car (partial-tree elements (length elements))))
(define (partial-tree elts n)
(if (= n 0)
(cons '() elts)
(let ((left-size (quotient (- n 1) 2)))
(let ((left-result (partial-tree elts left-size)))
(let ((left-tree (car left-result))
(non-left-elts (cdr left-result))
(right-size (- n (+ left-size 1))))
(let ((this-entry (car non-left-elts))
(right-result (partial-tree (cdr non-left-elts)
right-size)))
(let ((right-tree (car right-result))
(remaining-elts (cdr right-result)))
(cons (make-tree this-entry left-tree right-tree)
remaining-elts))))))))
Я искал решение в Интернете, и следующий веб-сайт, на мой взгляд, предлагает лучшее решение, но у меня возникают трудности с его осмыслением:
http://jots-jottings.blogspot.com/2011/12/sicp-exercise-264-constructing-balanced.html
Насколько я понимаю, процедура "частичное дерево" неоднократно вызывает три аргумента каждый раз, когда она вызывается - "эта запись", "левое дерево" и "правое дерево" соответственно. (и "Остальные-Эльты" только тогда, когда это необходимо - либо в самом первом вызове "частичного дерева", либо всякий раз, когда вызывается "не-левые")
- вызовы this-entry: car, cdr и cdr (левый результат)
- левые входящие вызовы: car, cdr и сам по себе с каждой длиной в два раза
- вызовы правого входа: car, сам с cdr(cdr(left-result)) в качестве аргумента и длина уменьшается вдвое
'left-entry' будет иметь базовые 2 log(n) шага, и все три аргумента будут вызывать 'left-entry' отдельно. поэтому он будет иметь троичную структуру и общее число шагов, которое я думал, будет похоже на 3^log(n). но решение говорит, что оно использует каждый индекс 1..n только один раз. Но разве "this-entry", например, не уменьшает один и тот же индекс на каждом узле отдельно от "right-entry"?
Я в замешательстве.. Далее, в части (а) сайта решения говорится:
"в не заканчивающемся случае частичное дерево сначала вычисляет количество элементов, которые должны войти в левое поддерево сбалансированного двоичного дерева размера n, затем вызывает частичное дерево с элементами и тем значением, которое оба производит такое поддерево и список элементов, не входящих в это поддерево. Затем в качестве значения для текущего узла берется заголовок неиспользуемых элементов.
Я считаю, что процедура делает эту запись перед левым деревом. Почему я не прав?
Это моя самая первая книга по CS, и мне еще не приходилось сталкиваться с основной теоремой. Это упоминается в некоторых решениях, но, надеюсь, я смогу решить вопрос, не используя его.
Спасибо за чтение, и я с нетерпением жду вашего любезного ответа,
Крис
1 ответ
Вы должны понять, как let
формы работают. В
(let ((left-tree (car left-result))
(non-left-elts (cdr left-result))
left-tree
ничего не "звонит". Он создается как новая лексическая переменная и ему присваивается значение (car left-result)
, Скобки вокруг него предназначены только для группировки элементов, описывающих одну переменную, введенную let
Форма: имя переменной и ее значение:
(let ( ( left-tree (car left-result) )
;; ^^ ^^
( non-left-elts (cdr left-result) )
;; ^^ ^^
Вот как понять, как работает рекурсивная процедура: нет.
Только не пытайтесь понять, как это работает; вместо этого проанализируйте, что он делает, предполагая, что он делает (для меньших случаев), что он должен делать.
Вот, (partial-tree elts n)
получает два аргумента: список элементов (предположительно для помещения в дерево) и длину списка. Возвращается
(cons (make-tree this-entry left-tree right-tree)
remaining-elts)
пара минусов дерева - результат преобразования, а остальные элементы, которые должны быть не оставлены, в самом верхнем вызове, если аргумент длины был правильным.
Теперь, когда мы знаем, что он должен делать, мы заглядываем внутрь. И действительно, если исходить из вышесказанного, то, что он делает, имеет смысл: вдвое сократить количество элементов, обработать список, вернуть дерево и оставшийся список (теперь не пустой), а затем обработать то, что осталось.
this-entry
это не дерево - это элемент, который размещен в узле дерева:
(let ((this-entry (car non-left-elts))
настройка
(right-size (- n (+ left-size 1))
Значит это n == right-size + 1 + left-size
, Это 1 элемент, который входит в сам узел, this-entry
элемент.
И поскольку каждый элемент попадает непосредственно в его узел, один раз, общее время выполнения этого алгоритма является линейным по количеству элементов во входном списке с использованием логарифмического пространства стека.