В элементарной арифметике очень часто встречается операция “факториал”: факториалом числа N называется произведение всех целых чисел от 1 до N включительно. На языке Scheme это определение выглядит так:
(define (factorial n)
(if (> n 0)
(* n (factorial (- n 1)))
1))Процесс вычисления результата этой операции можно проиллюстрировать следующей последовательностью выражений:
(factorial 5)
(* 5 (factorial 4))
(* 5 (* 4 (factorial 3)))
(* 5 (* 4 (* 3 (factorial 2))))
(* 5 (* 4 (* 3 (* 2 (factorial 1)))))
(* 5 (* 4 (* 3 (* 2 (* 1 (factorial 0))))))
(* 5 (* 4 (* 3 (* 2 (* 1 1)))))
(* 5 (* 4 (* 3 (* 2 1))))
(* 5 (* 4 (* 3 2)))
(* 5 (* 4 6))
(* 5 24)
120Определение операции, которое использует определяемую операцию, называется рекуррентным или рекурсивным. Встречаются и взаимно рекурсивные определения, например:
(define (чётное? x)
(if (= x 0) #t (нечётное? (- x 1))))
(define (нечётное? x)
(if (= x 0) #f (чётное? (- x 1))))Ключевым моментом при написании рекурсивных определений является условие остановки: почти всегда рекурсивное определение имеет вид
(define (нечто ...)
(if условие рекурсивная-часть нерекурсивная-часть))Без такого условия вычисление будет продолжаться бесконечно долго (или же до момента исчерпания доступной памяти).
В прошлой главе в одном из упражнений предлагалось реализовать отрицание, конъюнкцию и дизъюнкцию. Вообще говоря, эти операции встроены в язык Scheme: называются они not, and и or. Более того, конъюнкция и дизъюнкция являются специальными формами: их входы вычисляются слева направо до тех пор, пока они могут повлиять на итоговый результат. А именно: аргументы and вычисляются до первого встреченного #f, а аргументы or – до первого встреченного #t.
(and #f (display "Это не будет напечатано"))
(or #f #f (display "Это будет напечатано"))Также часто встречается специальная форма cond, имеющая вид
(cond
(условие_1 выражение_1)
(условие_2 выражение_2)
...
(условие_n выражение_n)
)которая вычисляет выражение, соответствующее самому первому истинному условию, если такие есть. Например, факториал мог быть реализован так:
(define (factorial n)
(cond
[(> n 0) (* n (factorial (- n 1)))]
[#t 1]))Существенным недостатком описанного выше способа вычисления факториала является расход памяти: выражение вида
(factorial N)в процессе вычисления разворачивается в выражение
(* N ... (* 2 (* 1 (factorial 0)))..)длина которого пропорциональна N.
Альтернативный, более эффективный, способ вычисления таков:
(define (factorial n)
(define (factorial-loop result k)
(if (> k n)
result
(factorial-loop (* result k) (+ k 1))))
(factorial-loop 1 1))Процесс вычисления выражения
(factorial 5)может быть проиллюстрирован следующей последовательностью:
(factorial 5)
(factorial-loop 1 1)
(factorial-loop 1 2)
(factorial-loop 2 3)
(factorial-loop 6 4)
(factorial-loop 24 5)
(factorial-loop 120 6)
120Здесь оба “входа” вспомогательной операции factorial-loop были использованы для хранения промежуточных результатов вычисления. Как видно из иллюстрации, расход памяти не зависит от величины входных данных (если говорить совсем строго, то в реализациях языка, поддерживающих арифметику целых чисел произвольной длины, вычисление факториала числа N требует количество памяти, пропорциональное N, для хранения самого факториала).
Ключевое отличие – рекурсивно определяемое выражение в процессе вычисления заменяется на себя же, только с другими значениями входных параметров, не использующими это выражение. Такой вид рекурсии называется хвостовым или циклическим.
сумма, для которой (сумма N) – сумма всех целых чисел от 1 до N включительно.сумма-цифр, вычисляющую сумму цифр поданного на вход числа.сумма-делителей, вычисляющую сумму целых положительных делителей поданного на вход числа.простое?, выполняющую проверку входного числа на простоту. Напомним, что простыми называются целые положительные числа, имеющие ровно два различных целых положительных делителя.НОД, вычисляющую наибольший общий делитель поданных на вход целых чисел. Считать, что (НОД 0 0) равен 0. Вам может пригодиться Алгоритм Евклида.@ 2016 arbrk1, all rights reversed