
- •Теоретический раздел лекции Тема 1. Программирование с использованием рекурсии
- •1.1. Cтратегии решения задачи разбиением ее на подзадачи
- •1.2. Программирование рекуррентных соотношений
- •Var z:extended;
- •1.3. Условия окончания рекурсии
- •1.4. О целесообразности использования рекурсии
- •Var I,X,y,z:word;
- •1.5 Правила выбора программной реализации рекуррентных соотношений
- •Тема 2. Задачи перебора вариантов
- •2.1. Модель дерева решений
- •2.2. Задача оптимального выбора (задача о рюкзаке)
- •2.3. Метод полного перебора двоичного дерева
- •Var wt,ct:extended;
- •Var j,k:byte;
- •If k in s then begin
- •Var j:byte;
- •Var j:byte;
- •Var wt1,oct1:byte;
- •2.4. Метод ветвей и границ
- •Var n,I:byte;
- •Var wt1,oct1:Extended;
- •Include(s,I);
- •If I in Sopt then writeln(I,a[I].W,a[I].C);
- •2.5. Эвристические методы
- •Тема 3. Поиск и сортировка массивов записей
- •3.1. Применимость сортировки и поиска
- •3.2. Массив записей и поиск в нем
- •Var m:word;
- •3.3. Сортировки массивов
- •Var c: mas; I,j,k:word;
- •Var m:word;
- •Var I,j:Word;
- •Var I,j,l,r:Word; X:Tk; w:Tzp;
- •Тема 4. Связанные списки с использованием рекурсивных данных
- •4.1. Список, стек, очередь
- •4.2. Списки на основе динамических массивов
- •Inherited create;
- •Var turn:Tlist; с1,c2:Tinf;
- •4.3. Рекурсивные данные и однонаправленные списки
- •Inherited create;
- •Var stec,st1,turn,tr1:Tlist; inf:Tinf;
- •4.4. Начальное формирование, добавление и удаление элементов однонаправленного списка
- •4.5. Разновидности связанных списков
- •Inf:Tinf;
- •Тема 5. Поиск и сортировки на связанных линейных списках
- •5.1. Поиск в однонаправленных списках
- •5.2. Сортировка однонаправленных списков
- •1 3Var Inf:tInf;
- •Тема 6. Использование линейных связанных списков
- •6.1. Вычисления арифметических выражений
- •Var ch,ch1,ch2,chr:char;
- •I:byte;ch,ch1:char;
- •6.2. Сложение больших целых чисел
- •Var u,V,s,t:byte;
- •6.3. Работа с разреженными матрицами
- •Inf:Tinf;
- •Inf:tInf;
- •Var proot,p:Ptree;
- •Var bl:boolean;
- •7.2. Бинарное дерево поиска
- •7.3. Основные операции с бинарным деревом поиска
- •Inf:tInf;
- •Var d1:Tree; c:Tinf; k:Tkey;
- •Var bl:Boolean;
- •Var m:Word;
- •Var p:Ttree; m:Word;
- •Тема 8. Хеширование
- •8.1. Что такое хеширование
- •8.2. Схема хеширования
- •Interface
- •Inf:Tinf;
- •8.4. Другие способы хеширования
- •Практический раздел Указания по выбору варианта
- •Индивидуальные практические работы и контрольные работы
- •Индивидуальная практическая работа №1. Программирование с использованием рекурсии
- •1.1. Понятие рекурсии
- •1.2. Порядок выполнения работы
- •1.2.1. Пример решения задачи
- •Индивидуальная практическая работа №2. Организация однонаправленного списка на основе рекурсивных типов данных в виде стека
- •2.1. Основные понятия и определения
- •Inf:tInf; // информация
- •Контрольная работа №1. Программирование с использованием деревьев на основе рекурсивных типов данных
- •1.1. Понятие древовидной структуры
- •Inf:tInf;
- •1.2. Компонент tTreeView
- •1.3. Бинарное дерево поиска
- •Основные операции с двоичным деревом поиска
- •1.4. Порядок написания программы
- •Inf:tInf;
- •Inherited Free;
- •Var tr:Ttree;
- •1.5. Индивидуальные задания
- •Курсовая работа
- •Литература
1.3. Условия окончания рекурсии
Подобно любым циклическим алгоритмам рекурсивные подпрограммы могут приводить к своеобразному зацикливанию, когда в процессе последовательного перехода к нижнему уровню не выполняется условие, обеспечивающее нахождение элементарной подзадачи. Наиболее надежный способ обеспечить окончание рекурсивной процедуры – ввести в нее некоторый параметр, например n>0, и при каждом рекурсивном обращении задавать n-1, проверяя условие n=0 как это организовано в вышеприведенном примере.
Важно также сделать оценку максимальной глубины рекурсии чтобы убедиться, что она достаточно мала и не приводит к переполнению программного стека.
1.4. О целесообразности использования рекурсии
Как мы уже заметили, рекурсивные алгоритмы особенно подходят для задач, допускающих рекурсивное разбиение на элементарные подзадачи. Однако это не означает, что для решения таких задач бесспорно употребление рекурсивных подпрограмм. Более того, иллюстрация программирования рекурсивных алгоритмов на неподходящих для этого примерах вызвало широко распространенное предубеждение, что их использование абсолютно неэффективно.
Рассмотрим в качестве такого неподходящего примера задачу вычисления чисел Фибоначчи, которые определяются следующим рекурсивным соотношением:
а) b0=0; b1=1;
б) bn=bn-1+bn-2.
В результате прямого переписывания этого алгоритма получаем изящную рекурсивную подпрограмму:
Листинг1.4
Function Fb(n:word):word;
begin
if n=0 then Fb:=0
else if n=1 then Fb:=1
else Fb:=Fb(n-1)+Fb(n-2)
end;
Однако ценность этой программы только в ее изяществе. Рассмотрим, например, как она будет работать при вызове F:=Fb(5). Каждое обращение к функции с n>1 приводит еще к двум активациям и число обращений растет как 2n-1. Дерево обращений для n=5 можно представить следующим образом:
Из этого представления видно, что не выполняется основное требование стратегии «разделяй и властвуй» – разбиение производится на зависимые подзадачи, решение которых при рекурсивной реализации многократно повторяются. Ясно, что такая программа практического интереса не представляет.
Числа Фибоначчи могут быть вычислены без рекурсии, с помощью более эффективной итерационной процедуры, используя стратегию «динамического программирования»:
Листинг1.5
Function Fb(n:word):word;
Var I,X,y,z:word;
begin if n=0 then Result:=0
else if n=1 then Result:=1 else
begin
x:=0; y:=1
for i:=2 to n do
begin
z:=x+y;
x:=y;
y:=z;
end end;
Result:=y;
end;
Здесь, согласно стратегии «динамического программирования» происходит запоминание в ячейках x,y решения двух предыдущих пересекающихся подзадач, и в результате нет повторов решения одинаковых под-подзадач их пересечения.
Подсчитайте сколько памяти и количество операций сравнения и сложения необходимо для вычисления b5, b10 и сравните с предыдущей программой.
Из этого примера следует, что не нужно применять рекурсию там, где есть очевидное итерационное (табличное) решение. В принципе, любую рекурсивную подпрограмму можно преобразовать в чисто итеративную (табличную). Однако во многих случаях это требует явной организации стека и преобразования настолько затуманивают суть программы, что ее бывает трудно понять. Решение следующей задачи является примером разумного применения рекурсии.
Задача о Ханойской башне. Имеются три стержня s1,s2,s3. На первом из них нанизаны n дисков различных диаметров, образующих правильную пирамиду чем выше расположен диск, тем меньше его диаметр. Требуется переместить всю башню на второй стержень, причем диски можно переносить по одному, нельзя помещать диск на диск меньшего диаметра, для промежуточного хранения можно использовать третий диск.
Тривиальная задача – башня из 1 диска, решается за один прием:
1) перенести диск с s1 на s2.
Элементарная подзадача – башня из двух дисков, решается в три приема: 1) перенести диск с s1 на s3
2) перенести диск с s1 на s2
перенести диск с s3 на s2
Рекурсивное соотношение:
Башня из n дисков представляется как башня из 2-х – один нижний и верхний, состоящий из n-1 дисков.
Рекурсивная процедура записывается следующим образом:
Листинг1.6
Procedure Hanoi(n,s1,s2,s3:word);
Begin
If n>0 then
begin
Hanoi(n-1,s1,s3,s2);
печать(перенести диск со стержня s1 на стержень s2);
Hanoi(n-1,s3,s2,s1);
end;
end;
Обращение: Hanoi(n,1,2,3).
В результате при n=3 будет выдан следующий алгоритм перемещения трех дисков:
перенести диск со стержня 1 на стержень2
перенести диск со стержня 1 на стержень3
перенести диск со стержня 2 на стержень3
перенести диск со стержня 1 на стержень2
перенести диск со стержня 3 на стержень1
перенести диск со стержня 3 на стержень2
перенести диск со стержня 1 на стержень2
Обращение
к процедуре:Hanoi(3,1,2,3)
вызовет
следующее
дерево активаций и печати: