Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Рекурсивные алгоритмы.rtf
Скачиваний:
3
Добавлен:
10.07.2019
Размер:
1.88 Mб
Скачать

Рекурсивный код и стек времени исполнения

Функция — это последовательность инструкций, выполняемых в ответ на ее вызов. Процесс выполнения начинается с того, что вызывающий блок заполняет активизирующую запись (activation record), которая включает спи­сок параметров и местоположение следующей инструкции, подлежащей вы­полнению после возврата из блока.

При вызове функции данные из активизирующей записи заталкиваются в стек, организуемый системой (стек времени исполнения). Данные объеди­няются с локальными переменными и образуют активизирующий фрейм” доступный функции.

При выходе из функции устанавливается местоположение следующей инструкции (рис. 10.4), а данные в стеке, соответствующие активизирующей записи, уничтожаются. Рекурсивная функция повторно вызывает саму себя, используя всякий раз модифицированный список параметров. При этом последовательность активизирующих записей заталкивается в стек до тех пор, пока не будет достигнуто условие останова. Последовательное выталкивание этих записей и дает нам наше рекурсивное решение. Функция вычисления факториала иллюстрирует использование активизирующих записей.

Стек времени исполнения

С помощью примера вычисления факториала от 4 мы проиллюстрируем использование активизирующих записей и стека, создаваемого во время вы полнения рекурсивной функции. Начальный вызов факториала производите! из главной программы. После выполнения функции управление возвращается в точку RetLockl, где переменной N присваивается значение 24(4!):

void main (void)

{

int n;

// поместить в стек запись с помощью вызова FACTORIAL(4)

// RetLockl - адрес присвоения n = FACTORIAL(4)

n=FACTORIAL(4);

­ RetLockl *

}

Рекурсивные вызовы в функции FACTORIAL возвращают управление я точку RetLock2, где вычисляется произведение п * (п-1)!. Результат вычисления запоминается в переменной temp, чтобы помочь читателю проследить код и продемонстрировать стек времени исполнения:

long FACTORIAL(long n)

{

int temp;

if(n=0}

return 1;

// вытолкнуть из стека активизирующую запись

else

{

// поместить в стек активизирующую запись с помощью вызова

// FACTORIAL(n-1)

// Retlock2 - адрес вычисления n * FACTORIAL(n-1).

temp=n*FACTORIAL(n-1);

­ RetLock2

return temp; // вытолкнуть из стека активизирующую запись

}

Для функции FACTORIAL активизирующая запись имеет два поля.

Выполнение FACTORIAL(4) инициирует последовательность из пяти вызовов. На рис. 10.5 показаны активизирующие записи для каждого вызова. Записи входят в стек снизу вверх вместе с вызовом из главной процедуры, занимая нижнюю часть стека.

При обращении к функции FACTORIAL с параметром 0 возникает условие останова, и начинается выполнение последовательности операторов возврата. Когда из стека выталкивается самая верхняя активизирующая запись, управление передается в точку возврата. Очистка стека от активизирующих записей описывается следующими операциями.

Решение задач с помощью рекурсии

Многие вычислительные задачи имеют весьма простую и изящную фор­мулировку, которая непосредственно переводится в рекурсивный код. В разделе 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);

}

}