Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
progF_pos.doc
Скачиваний:
36
Добавлен:
11.05.2015
Размер:
1.51 Mб
Скачать

7. Рекурсия

7.1. Простая рекурсия

Объект называется рекурсивным, если он содержит сам себя или определен с помощью самого себя.

Рекурсия встречается не только в математике, но и в обыденной жизни. Каждый сталкивается с рекурсией, когда стоит с зеркальцем перед большим зеркалом. Рекурсия встречается обычно и в природе: деревья имеют рекурсивное строение (ветки образуются из других веток), реки образуются из впадающих в них рек. Клетки делятся рекурсивно. Продолжение жизни связано с рекурсивным процессом. Молекулы ДНК и вирусы размножаются, копируя себя, живые существа имеют потомство, которое, в свою очередь, тоже имеет потомство и т. д. Рекурсия распространена и в языке, и в поведении так же, как и в способах рассуждения и познания. Рекурсия в языке, например, может быть в структуре или в содержании:

“Петя сказал, что Вася сказал, что...”

“Знаю, что знаю, но не помню”

“Сделать; заставить сделать; заставить, чтобы заставили сделать;...”

“Замени x этим предложением”

“Запомни и передай это сообщение”

. . .

Литературным примером может служить "рассказ в рассказе", как-то: известное стихотворение "У попа была собака,..." , роман Яна Потоцкого "Рукопись, найденная в Сарагосе", рассказ Хулио Кортасара “Непрерывность парков” или рассказ Виктора Пелевина ”Водонапорная башня” (и по форме - состоит из одного предложения, и по содержанию). Музыкальные формы и действия также могут быть рекурсивными во многих отношениях (например, канон, в котором мелодия сопровождается той же мелодией с задержкой, и другие). Целенаправленное поведение и решение проблем также являются рекурсивными процессами.

Лисп основан на рекурсивном подходе.

Мы будем говорить, что применяется простая рекурсия, если рекурсивный вызов соответствует простому циклу в процедурном программировании.

Функция member проверяет, принадлежит ли элемент списку

(defun member (x s)

(cond ((null s) nil)

((equal (first s) x) s)

(t (member x (rest s))))

)

С помощью функции trace можно устроить трассировку member.

(trace member)

(member 5 '(1 2 5 6))

; 1> MEMBER called with args:

; 5

; (1 2 5 6)

; | 2> MEMBER called with args:

; | 5

; | (2 5 6)

; | | 3> MEMBER called with args:

; | | 5

; | | (5 6)

; | | 3< MEMBER returns value:

; | | (5 6)

; | 2< MEMBER returns value:

; | (5 6)

; 1< MEMBER returns value:

; (5 6)

(5 6)

В рекурсивном определении существенен порядок следования условий. При определении порядка следования основным моментом является то, что сначала проверяются всевозможные условия окончания, а затем ситуации, требующие продолжения вычислений. При помощи условий окончания последовательно проверяются все особые случаи и программируются соответствующие результирующие выражения. На исключенные таким образом случаи можно в последующем не обращать внимания.

Предикат member в XLisp определен в другом виде, чем мы его представили. Вместо предиката сравнения equal используется предикат eql, поэтому встроенный member не может находить элементы, являющиеся списками. Но можно вместо предиката eql, являющегося умолчанием, задать при вызове с помощью ключевого параметра: test другой предикат. Например, в следующем примере для сравнения элементов используется предикат equal, который применим и к спискам:

(member ‘(c d) ‘((a b) (c d)) :test #’equal) ==> ((c d))

Функция append объединяет два списка

(defun append (x y)

(if (null x) y

(cons (first x) (append (rest x) y)))

)

> (trace append)

(APPEND)

> (append '(1 2) '(3 4 5))

; 4> APPEND called with args:

; (1 2)

; (3 4 5)

; | 5> APPEND called with args:

; | (2)

; | (3 4 5)

; | | 6> APPEND called with args:

; | | NIL

; | | (3 4 5)

; | | 6< APPEND returns value:

; | | (3 4 5)

; | 5< APPEND returns value:

; | (2 3 4 5)

; 4< APPEND returns value:

; (1 2 3 4 5)

(1 2 3 4 5)

Соединение списков можно определить и по-другому:

(defun append1 (x y)

(if (null x) y

(append1 (rest x) (cons (first x) y)))

)

В этом случае второй параметр используется и как накапливающий результат. Отметим, что список x входит в результирующий список в обращенном виде.

Функция remove удаляет элемент из списка

; удаляет первое вхождение элемента

(defun remove (x s)

(cond ((null s) nil)

((eql x (first s)) (rest s))

(t (cons (first s) (remove x (rest s)))))

)

> (remove 2 '(1 2 3 4))

(1 3 4)

> (remove 'b '(a (b c)))

(A (B C))

> (remove '(a b) '((a b) (c d)))

((A B) (C D))

Встроенная функция remove использует предикат eql. Можно задать непосредственно предикат сравнения для функции remove при её вызове ключевым параметром :test таким же образом, как и для функции member. Приведем пример:

(remove ‘(a b) ‘((a b) (c d)) :test #‘equal) ==> ((c d))

Следующий вариант функции remove удаляет все вхождения элемента.

(defun remove1 (x s)

(cond ((null s) nil)

((eql x (first s)) (remove1 x (rest s)))

(t (cons (first s) (remove1 x (rest s)))))

)

Функция reverse обращает список

(defun reverse1 (s)

(if (null s) nil

(append (reverse1 (rest s)) (list (first s))))

)

(trace reverse1)

(REVERSE1)

> (reverse1 '(a b c))

; 1> REVERSE1 called with arg:

; (A B C)

; | 2> REVERSE1 called with arg:

; | (B C)

; | | 3> REVERSE1 called with arg:

; | | (C)

; | | | 4> REVERSE1 called with arg:

; | | | NIL

; | | | 4< REVERSE1 returns value:

; | | | NIL

; | | 3< REVERSE1 returns value:

; | | (C)

; | 2< REVERSE1 returns value:

; | (C B)

; 1< REVERSE1 returns value:

; (C B A)

(C B A)

Использование накапливающего параметра дает второй вариант:

(defun reverse2 (s)

(labels

((f (x y)

(if (null x) y

(f (rest x) (cons (first x) y)))

))

(f s nil))

)

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]