На этом шаге мы введем понятие параллельной рекурсии и проиллюстрируем ее использование.
Рекурсию называют параллельной, если она встречается одновременно в нескольких аргументах функции.
"Вот два правила, которыми можно руководствоваться, решая вопрос о целесообразности применения рекурсии:
(DEFUN COUNT (LAMBDA (LST) (COND ( (NULL LST) 0 ) ( (ATOM LST) 1 ) ( T (+ (COUNT (CAR LST)) (COUNT (CDR LST))) ) ) ))
Отметим, что если применять эту функцию к списку списков, то элементы NIL, которые заключают каждый список, будут также учитываться!
(DEFUN COUNTATOMS (LAMBDA (LST) (COND ( (NULL LST) 0 ) ( (ATOM LST) 1 ) ( T (+ (COUNTATOMS (CAR LST)) (COUNTATOMS (CDR LST))) ) ) ))
(DEFUN DEPTH (LAMBDA (X)
(COND ( (ATOM X) 1 )
( T (MAX (+ 1 (DEPTH (CAR X)))
(DEPTH (CDR X))) )
)
))
; --------------------
(DEFUN MAX (LAMBDA (X Y)
(COND ( (> X Y) X )
( T Y )
)
))
(DEFUN COPY (LAMBDA (EXPN) (COND ( (ATOM EXPN) EXPN ) ( T (CONS (COPY (CAR EXPN)) (COPY (CDR EXPN))) ) ) ))
(DEFUN ADD (LAMBDA (L) (COND ( (NULL L) 0 ) ( (ATOM (CAR L)) (+ (CAR L) (ADD (CDR L)))) ( T (+ (ADD (CAR L)) (ADD (CDR L))) ) ) ))
(DEFUN MEMBER3 (LAMBDA (X LST) (COND ( (NULL LST) NIL) ( (COND ( (ATOM (CAR LST)) (COND ( (EQ X (CAR LST)) LST ) ( T (MEMBER3 X (CDR LST)) )) ) ( T (MEMBER3 X (CAR LST)) ) ) ) ( T (MEMBER3 X (CDR LST)) ) ) ))
Функция SUPERREVERSE обращает голову списка, формирует из него список и присоединяет к нему спереди обращенный хвост.
(DEFUN SUPERREVERSE (LAMBDA (L) ( (ATOM L) L ) ( (NULL (CDR L)) (CONS (SUPERREVERSE (CAR L)) NIL) ) ( APPEND (SUPERREVERSE (CDR L)) (SUPERREVERSE (CONS (CAR L) NIL)) ) )) ; ----------------------- (DEFUN APPEND (LAMBDA (X Y) ; Функция APPEND возвращает список, состоящий из ; элементов списка X, добавленных к списку Y (COND ( (NULL X) Y ) ( T (CONS (CAR X) (APPEND (CDR X) Y)) ) ) ))
(DEFUN ONE-RANGE (LAMBDA (LST) ( (NULL LST) NIL ) ( (ATOM LST) (CONS (CAR LST) NIL) ) ( APPEND (ONE-RANGE (CAR LST)) (ONE-RANGE (CDR LST)) ) )) ; ----------------------- (DEFUN APPEND (LAMBDA (X Y) ; Функция APPEND возвращает список, состоящий из ; элементов списка X, добавленных к списку Y (COND ( (NULL X) Y ) ( T (CONS (CAR X) (APPEND (CDR X) Y)) ) ) ))
Функция ONE-RANGE объединяет (функцией APPEND) "ужатую" в один уровень голову списка и "ужатый" хвост. Если голова списка является атомом, то из него формируется список, поскольку аргументы функции APPEND должны быть списками.
(DEFUN EQUAL1 (LAMBDA (L M) (COND ( (NULL L) (NULL M) ) ( (ATOM L) (AND (ATOM M) (EQ L M)) ) ( (ATOM M) NIL ) ( T (AND (EQUAL1 (CAR L) (CAR M)) (EQUAL1 (CDR L) (CDR M))) ) ) ))
(DEFUN LISTP (LAMBDA (X) (COND ( (NULL X) T ) ( (ATOM X) NIL ) ( (OR (ATOM (CAR X)) (LISTP (CAR X))) (LISTP (CDR X)) ) ( T NIL ) ) ))
Тестовые примеры:
$ (LISTP '((A B) (B . C) C))
NIL
$ (LISTP '((A . (B C)) (B . (C A))))
T
Реализуем классическую восточную игру "Ханойские башни". Игра состоит в следующем. Используются три вертикальные стержня А, В и С и совокупность N круглых дисков разной величины с отверстием. В начальном состоянии диски нанизаны по порядку в соответствии со своими размерами на стержень А.
Целью является перенесение всех дисков на стержень В. Однако, диски переносятся не в произвольном порядке, при переносе нужно выполнять следующие правила:
Третий стержень можно использовать как вспомогательный (промежуточный). Если он свободен или там лежит больший диск, то на него можно переложить очередной диск на то время, пока переносится ниже лежащий диск. В этой идее и содержится решение игры.
Алгоритм задачи "Ханойские башни":
(DEFUN HANOI (LAMBDA (VYSOTA)
(PERENOS A B C VYSOTA) (QUOTE Готово!)
))
; -------------------------------------
(DEFUN PERENOS (LAMBDA (IZ W VSPOMOGAT N)
( (EQ N 1) (PRIN1 IZ) (PRIN1 " --> ") (PRINT W) )
( (PERENOS IZ VSPOMOGAT W (- N 1))
(PRIN1 IZ) (PRIN1 " --> ") (PRINT W)
(PERENOS VSPOMOGAT W IZ (- N 1))
)
))
Тестовый пример:
$ (HANOI 3)
А --> В
А --> С
В --> С
А --> В
С --> А
С --> В
А --> В
Готово!
Задачи для башен, состоящих из одного или двух дисков, решаются просто. Задача для трех дисков требует уже больше переносов.
Можно ли указать нерекурсивное решение задачи о Ханойской башне? Ответ положителен. Хотя его обоснование значительно сложнее, чем рекурсивное решение.
Прямое решение нашли П.Бьюнеман и Л.Леви. Алгоритм последовательно выполняет следующие действия [2, с.64-65].
На следующем шаге мы рассмотрим взаимную рекурсию.