На этом шаге мы введем понятие параллельной рекурсии и проиллюстрируем ее использование.
Рекурсию называют параллельной, если она встречается одновременно в нескольких аргументах функции.
"Вот два правила, которыми можно руководствоваться, решая вопрос о целесообразности применения рекурсии:
(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].
На следующем шаге мы рассмотрим взаимную рекурсию.