Решение задач с помощью рекурсии
Многие вычислительные задачи имеют весьма простую и изящную формулировку, которая непосредственно переводится в рекурсивный код. В разделе 1 рассмотрен пример, про Ханойскую башню. В этом разделе мы расширим диапазон примеров и рассмотрим рекурсивное определение алгоритма бинарного поиска.
Бинарный поиск
При бинарном поиске берется некоторый ключ и просматривается упорядоченный массив из n элементов на предмет совпадения с этим ключом. Функция возвращает индекс совпавшего с ключом элемента или –1 при отсутствии такового. Алгоритм бинарного поиска может быть описан рекурсивно.
Допустим, отсортированный список А характеризуется нижним граничным индексом low и верхним – high. Имея ключ, мы начинаем искать совпадение в середине списка (индекс mid).
mid = (low+high)/2 Сравнить A[mid] с ключом
Если совпадение произошло, мы имеем условие останова, что позволяет нам прекратить поиск и возвратить индекс mid.
Если совпадение не происходит, можно воспользоваться тем фактом, что список упорядочен, и ограничить диапазон поиска "нижним подсписком" (слева от mid) или "верхним подсписком" (справа от mid).
Бели ключ < A[mid], совпадение может произойти только в левой половине списка в диапазоне индексов от low до mid-1.
Бели ключ > A[mid], совпадение может произойти только в правой половине списка в диапазоне индексов от mid+1 до high.
Шаг рекурсии направляет бинарный поиск для продолжения в один из подсписков. Рекурсивный процесс просматривает все меньшие и меньшие списки. В конце концов поиск заканчивается неудачей, если подсписки исчезли. Это происходит тогда, когда верхний предел списка становится меньше чем нижний предел. Условие low>high — второе условие останова. В этом случае алгоритм возвращает -1.
|
|
Бинарный поиск (рекурсивная форма). В шаблонной версии бинарного поиска в качестве параметров используется массив элементов типа Т, значение ключа, а также верхний и нижний граничные индексы. Оператор IF обрабатывает два условия останова: 1) совпадение произошло; 2) ключевого значения нет в списке. В блоке ELSE оператора IF выполняется шаг рекурсии, который направляет дальнейший поиск в левый (ключ<А[mid]) или в правый подсписок (ключ>А[mid]). Тот же алгоритм применяется по принципу "разделяй и властвуй" к последовательности все меньших интервалов, пока не произойдет успех (совпадение) или неудача.
// рекурсивная версия бинарного поиска ключевого значения
//в упорядоченном массиве А
template <clasa T>
int BinSearch{t A[], int low, int high, Т key)
{
int mid; J
Т midvalue; ;1
|
|
// условие останова:! ключ не найден
if (low > high) return (-1);
// сравнить ключ с элементом в середине списка.
// если совпадения нет, разделить на подсписки.
// применить процедуру бинарного поиска к подходящему подсписку
else
{
mid = (low+high)/2;
midvalue = A[mid]; // условие останова: ключ найден
if (key == midvalue)
return mid; // ключ найден по индексу mid
// просматривать левый подсписок, если key < midvalue;
//в противном случае - правый подсписок
else if (key < midvalue)
// шаг рекурсии
return BinSearch(A, low, mid-1, key);
else
// шаг рекурсии
return BinSearch(A, mid+1, high, key);
}
}
Введение
Рекурсивным называется объект, частично состоящий или определяемый с помощью самого себя. Рекурсия встречается не только в математике, но и в повседневной жизни.
Рекурсивные определения в математике представляют со мощный аппарат. Вот несколько общеизвестных примеров: натуральные числа, деревья и определенные функции.
1. Натуральные числа:
а) 0 есть натуральное число,
б) число, следующее за натуральным, есть натуральное число.
2. Деревья:
а) 0 есть дерево (его называют "пустым деревом"),
б) если t1 и t2 – деревья, то построение, содержащее вершину с двумя ниже расположенными деревьями, опять же дерево.
3. Функция "факториал" п\ (для неотрицательных целых чисел):
|
|
а) 0! = 1,
б) n>0:n! = n*(n- 1)!
Мощность рекурсивного определения заключается, очевидно, в том, что оно позволяет с помощью конечного высказывания определить бесконечное множество объектов. Аналогично с помощью конечной рекурсивной программы можно описать бесконечное вычисление, причем программа не будет содержать явных повторений. Однако рекурсивные алгоритмы лучше всего использовать, если в решаемой задаче, вычисляемой функции или структуре обрабатываемых данных рекурсия уже присутствует явно. В общем виде рекурсивную программу Р можно выразить как некоторую композицию Р из множества операторов S (не содержащих Р) и самой Р:
P=P[S,P]. (3.1)
Для выражения рекурсивных программ необходимо и достаточно иметь понятие процедуры или подпрограммы, поскольку они позволяют дать любому оператору имя, с помощью которого к нему можно обращаться. Если некоторая процедура Р содержит явную ссылку на саму себя, то ее называют прямо рекурсивной, если же Р ссылается на другую процедуру Q, содержащую (прямую и косвенную) ссылку на Р, то Р называют косвенно рекурсивной. Поэтому по тексту программы рекурсивность не всегда явно определима.
|
|
Как правило, с процедурой связывают множество локальных объектов, т. е. множество переменных, констант, типов и процедур, которые определены только в этой процедуре и вне ее не существуют или не имеют смысла. При каждой рекурсивной активации такой процедуры порождается новое множество локальных связанных переменных. Хотя они имеют те же самые имена, что и соответствующие элементы локального множества предыдущего "поколения" этой процедуры, их значения отличны от последних, а любые конфликты по именам разрешаются с помощью правил, определяющих область действия идентификаторов: идентификатор всегда относится к самому последнему порожденному множеству переменных. Это же правило справедливо и для параметров процедуры, по определению связанных с самой процедурой.
Подобно операторам цикла, рекурсивные процедуры могут приводить к незаканчивающимся вычислениям, и поэтому на эту проблему следует особо обратить внимание. Очевидно, чтобы работа когда-либо завершилась, необходимо, чтобы рекурсивное обращение к Р управлялось некоторым условием, которое в какой-то момент перестает выполняться. Поэтому более точно схему рекурсивных алгоритмов можно представить в из следующих форм:
Р = IF В THEN P [S, P] END,
P = Р [S, IF В THEN P END].
Основной способ доказательства конечности некоторого повторяющегося процесса таков:
1. Определяется функция f(х) (х – множество переменных) такая, что из f(х) < 0 следует истинность условия окончания цикла (с предусловием или постусловием).
2. Доказывается, что при каждом прохождении цикла f(x) уменьшается.
Аналогично доказывается и окончание рекурсии – noказывается, что Р уменьшает f(x), такую, что из f(x) £ 0 следует –В. В частности, наиболее надежный способ обеспечить окончание процедуры – ввести в нее некоторый параметр (значение), назовем его n, и при рекурсивном обращении к Р в качестве пара задавать n – 1. Если в этом случае в качестве В используется n>0, то окончание гарантировано. Это опять же выражается двумя схемами:
Р(п) = IF п > О THEN P[S, Р (n – 1)] END,
P(р) = Р [S, IF п > О THEN Р ( п – 1) END].
В практических приложениях важно убедиться, что максимальная глубина рекурсий не только конечна, но и достаточно, Дело в том, что каждая рекурсивная активация процедуры P требует памяти для размещения ее переменных. Кроме этих локальных переменных нужно еще сохранять текущее "состояние вычислений", чтобы можно было вернуться в него по окончании новой активации Р.
Дата добавления: 2018-10-26; просмотров: 210; Мы поможем в написании вашей работы! |
Мы поможем в написании ваших работ!