Кажется, я нашел общий способ преобразования любой рекурсивной процедуры в хвостовую рекурсию:
- Определите вспомогательную подпроцедуру с дополнительным параметром «result».
- Примените то, что будет применено к возвращаемому значению процедуры к этому параметру.
- Вызовите эту вспомогательную процедуру, чтобы начать. Начальным значением для параметра «result» является значение для точки выхода рекурсивного процесса, поэтому итоговый итерационный процесс начинается с того места, где рекурсивный процесс начинает сокращаться.
Например, вот оригинальная рекурсивная процедура для преобразования ( упражнение SICP 1.17 ):
(define (fast-multiply a b)
(define (double num)
(* num 2))
(define (half num)
(/ num 2))
(cond ((= b 0) 0)
((even? b) (double (fast-multiply a (half b))))
(else (+ (fast-multiply a (- b 1)) a))))
Вот преобразованная хвосто-рекурсивная процедура ( упражнение SICP 1.18 ):
(define (fast-multiply a b)
(define (double n)
(* n 2))
(define (half n)
(/ n 2))
(define (multi-iter a b product)
(cond ((= b 0) product)
((even? b) (multi-iter a (half b) (double product)))
(else (multi-iter a (- b 1) (+ product a)))))
(multi-iter a b 0))
Может кто-то доказать или опровергнуть это?
algorithms
logic
recursion
lisp
nalzok
источник
источник
b
степени 2 показывает, что изначально установкаproduct
на 0 не совсем верна; но изменение его на 1 не работает, когдаb
это странно. Может быть, вам нужно 2 разных параметра аккумулятора?Ответы:
Ваше описание вашего алгоритма действительно слишком расплывчато, чтобы оценить его на данный момент. Но вот некоторые вещи для рассмотрения.
КПС
Фактически, есть способ преобразовать любой код в форму, которая использует только хвостовые вызовы. Это преобразование CPS. CPS ( Continuation-Passing Style ) - это форма выражения кода, передавая каждой функции продолжение. Продолжением является абстрактное понятие, представляющее «остальную часть вычисления». В коде , выраженной в виде CPS, естественный способ материализовать продолжение является как функция , которая принимает значение. В CPS вместо функции, возвращающей значение, она применяет функцию, представляющую текущее продолжение, к функции, «возвращаемой» функцией.
Например, рассмотрим следующую функцию:
Это может быть выражено в CPS следующим образом:
Это уродливо и часто медленно, но у него есть определенные преимущества:
TCO
Мне кажется, что единственная причина, связанная с хвостовой рекурсией (или хвостовыми вызовами в целом), заключается в целях оптимизации хвостовых вызовов (TCO). Поэтому я думаю, что лучше задать вопрос: «Может ли мой код преобразования преобразовать с помощью хвостового вызова?».
Если мы еще раз рассмотрим CPS, одной из его характеристик является то, что код, выраженный в CPS, состоит исключительно из хвостовых вызовов. Поскольку все является хвостовым вызовом, нам не нужно сохранять точку возврата в стек. Таким образом, весь код в форме CPS должен быть оптимизирован с помощью хвостового вызова, верно?
Ну, не совсем. Видите ли, хотя может показаться, что мы удалили стек, все, что мы сделали, это просто изменили способ его представления. Стек теперь является частью замыкания, представляющего продолжение. Таким образом, CPS волшебным образом не делает весь наш код оптимизированным.
Так что, если CPS не может сделать все TCO, существует ли преобразование специально для прямой рекурсии, которое может? Нет не вообще. Некоторые рекурсии линейны, а некоторые нет. Нелинейные (например, древовидные) рекурсии просто должны где-то поддерживать переменное количество состояний.
источник