
- •«Рекурсивные алгоритмы» Содержание:
- •Теоретическое введение:
- •Тексты программ с комментариями;
- •Результаты выполнения программ. Теоретическое введение.
- •Задача поиска максимума.
- •Задача рисования меток на линейке.
- •Задача о Ханойских башнях.
- •Переместить верхние n-1 диск со столбика a на столбик b, используя столбик c как вспомогательный.
- •Переместить оставшийся нижний диск со столбика a на столбик c.
- •Переместить n-1 диск со столбика b на столбик c, используя столбик a как вспомогательный.
- •Тексты программ с комментариями.
- •Int Maximum(int a[10], int l, int r)//заголовок ф-ии
- •Результаты выполнения программ.
«Рекурсивные алгоритмы» Содержание:
-
Теоретическое введение:
а) Задача поиска максимума;
б) Задача рисования меток на линейке;
в) Задача о Ханойских башнях;
-
Тексты программ с комментариями;
-
Результаты выполнения программ. Теоретическое введение.
Рекурсивным называется объект, который частично определяется через самого себя. Для создания рекурсивных алгоритмов необходимо и достаточно наличие понятия функции. Это вытекает из того, что функции позволяют дать любой последовательности действий (операторов) имя, с помощью которого можно будет эту последовательность действий вызывать из любого места программы и из самой этой функции, что называется рекурсивным вызовом.
Во множестве разрабатываемых программ используется два рекурсивных вызова, каждый из которых работает приблизительно с половиной входных данных. Эта рекурсивная схема – вероятно, наиболее важный случай хорошо известного метода «разделяй и властвуй» разработки алгоритмов, который служит основой для важнейших алгоритмов
Задача поиска максимума.
Рассмотрим в качестве примера задачу отыскания максимального из N элементов, сохраненных в массиве a[0],…, a[N-1]. Эту задачу легко выполнить за один проход массива:
for (t=a[0], i=1; i<N; i++)
if (a[i]>t) t=a[i];
Рекурсивное решение типа «разделяй и властвуй» (функция Maximum ), приведенное в примере 1 и использующееся в программе 1 – еще один простой
(хотя и совершенно иной) алгоритм решения той же задачи; он использовался только с целью иллюстрации концепции «разделяй и властвуй».
Пример 1:
-------------------------------------------------------------------------------------------------------
int Maximum(int a[10], int l, int r)//заголовок ф-ии
{//a-исходный массив, l-индекс 1-го эл-та, r-индекс //последнего эл-та
if (l==r) return a[l]; //если массив состоит из 1-го //эл-та, то он и есть max
int m=(l+r)/2;//делим массив на 2 подмассива
int u=Maximum(a,l,m);//ищем в 1-ом максимальный эл-т
int v=Maximum(a,m+1,r);//во 2-ом также ищем //максимальный эл-т
if (u>v) return u; else return v;//сравнивая их, возвращаем максимальный
}
Эта функция делит массив a[l],…,a[r] на массивы a[l],…,a[m] и a[m+1],…,a[r], находит максимальные элементы в обоих частях (рекурсивно) и возвращает больший из них в качестве максимального элемента всего массива. Если размер массива является четным числом, обе части имеют одинаковые размеры, если же нечетным, эти размеры различаются на 1.
-------------------------------------------------------------------------------------------------------
Чаще всего подход «разделяй и властвуй» используют из-за того, что он обеспечивает более быстрые решения, чем простые итерационные алгоритмы; кроме того, данный подход заслуживает подробного исследования, поскольку он способствует пониманию сущности определенных базовых вычислений.
На рисунке 1 показаны рекурсивные вызовы, выполняемые при запуске программы 1 применительно к примеру массива. Структура программы кажется сложной, но обычно об этом можно не беспокоится – для проверки программы мы полагаемся на метод математической индукции, а для анализа ее производительности используется рекуррентное соотношение.
Рисунок 1:
-------------------------------------------------------------------------------------------------------
0 1 2 3 4 5 6 7 8 9 10
T I N Y E X A M P L E
Y max(0,10)
Y max(0,5)
T max(0,2)
T max(0,1)
T max(0,0)
I max(1,1)
N max(2,2)
Y max(3,5)
Y max(3,4)
Y max(3,3)
E max(4,4)
X max(5,5)
P max(6,10)
P max(6,8)
M max(6,7)
A max(6,6)
M max(7,7)
P max(8,8)
L max(9,10)
L max(9,9)
E max(10,10)
-------------------------------------------------------------------------------------------------------
Как обычно, сам код предполагает проверку правильности вычисления методом индукции:
-
Он явно и немедленно отыскивает максимальный элемент массива, размер которого равен 1.
-
Для N>1 код разделяет массив на два, размер каждого из которых меньше N, исходя из индуктивного предложения, находит максимальные элементы в обеих частях и возвращает большее из этих двух значений, которое должно быть максимальным значением для всего массива.
Более того, рекурсивную структуру программы можно использовать для исследования характеристик ее производительности.
Лемма 1. Рекурсивная функция, которая разделяет задачу размерности N на две независимые (непустые) решающие ее части, рекурсивно вызывает себя менее N раз.
Если одна часть имеет размерность k, а другая – N-k, то общее количество рекурсивных вызовов используемой функции равно:
Р
ешение
T=N-1
можно получить непосредственно методом
индукции. Если сумма размеров частей
меньше N,
доказательство того, что количество
вызовов меньше чем N-1
вытекает из тех же рассуждений по методу
индукции. Аналоги-
чными рассуждениями можно подтвердить справедливость данного утверждения и для общего случая.
На рисунке 2 показана структура (дерево) алгоритма «разделяй и властвуй» для поиска максимума. Эта структура является рекурсивной: верхний узел содержит размер входного массива; структура левого подмассива изображена слева, а правого – справа. На рисунке 2 также показано это же дерево, но с узлами, помеченными возвращаемым значением из соответствующего обращения к функции.
Рисунок 2:
-------------------------------------------------------------------------------------------------------
Алгоритм «разделяй и властвуй» разделяет представляющий задачу массив, размер которого равен 11, на массивы с размерами 6 и 5, массив с размером 6 – на два массива с размерами 3 и т.д., пока не будет получен массив с размером 1
(верхний). Каждая окружность на этих схемах представляет вызов рекурсивной функции для расположенных непосредственно под ней узлов, связанных с ней линиями (квадраты представляют вызовы, для которых рекурсия завершается). На схеме в центре показано значение индекса в середине файла, который использовался для выполнения разделения; на нижней схеме показано возвращаемое значение.
-------------------------------------------------------------------------------------------------------