Третьего вызова происходит возврат в вызывающую функцию (т.е. во второй экземп-

ляр "print_backwards()"), см. рис. 3.

Рис. 3. Возврат из 3-го экземпляра функции

"print_backwards()" во второй.

Рис. 4.. Завершение выполнения программы.

Второй экземпляр "print_backwards()" завершается, но перед завершением вы-

водит на экран символ "i". В свою очередь, первый экземпляр функции перед завер-

шением работы напечатает на экране символ "H" (рис. 4).

Для организации вызовов функций и создания автоматических переменных в

программах на Си++ отводится специальная область памяти – стек. Память, необхо-

Димая для очередного вызова функции, выделяется в верхней части стека (в старших

Адресах). При завершении функции размер стека уменьшается на соответствующую

Величину. Изменение состояния программного стека для рассмотренного выше при-

Мера показано на рис. 5.

Рис. 5.. Последовательность состояний стека программы 2.1 применительно к

Тестовому примеру.

Стек в программах на Си++ используется при вызове всех функций, а не только

Рекурсивных. Стек, как и связный список, является одной из разновидностей абст-

рактных типов данных. Характерная особенность стека – соответствие принципу "по-

следним прибыл – первым обслужен" (в отличие от стека, очередь является примером

абстрактного типа данных, действующего по принципу "последним прибыл – послед-

ним обслужен").

92

Еще три примера рекурсии

В этом параграфе приводятся три примера рекурсивных функций. В 3-й лекции

Рассматривалась функция для вычисления факториала целого положительного числа

(лекция 3, программа 3.1). Ниже приведен рекурсивный аналог этой функции:

Int factorial(int number)

{

if (number < 0)

{

cout << "\nОшибка - отрицательный аргумент факториала\n";

exit(1);

}

else if (number == 0)

return 1;

return number*factorial(number - 1);

}

Фрагмент программы 4.1.

В качестве второго примера (фрагмент программы 4.2) дана функция, возводя-

щая свой первый параметр (типа "double") в степень с показателем, который переда-

Ется в качестве второго параметра (неотрицательное целое число).

Double raised_to_power(double number, int power)

{

if (power < 0)

{

cout << "\nОшибка - отрицательный показатель степени\n";

exit(1);

}

else if (power == 0)

return 1.0;

return number*raised_to_power(number, power - 1);

}

Фрагмент программы 4.2.

В рекурсивных функциях из программ 4.1 и 4.2 особое внимание уделено за-

щите от "бесконечного вызова ". Эта защита основана на проверке параметров, обес-

Печивающей выполнение вычислений только для корректных значений переданных

Параметров. Т.о. гарантируется, что будут произведены корректные вычисления и в

Конечном счете произойдет возврат из функции. При недопустимых значениях пара-

Метров выдается сообщение об ошибке и выполняется возврат из функций или завер-

Шение работы программы.

Третий пример (фрагмент программы 4.3) – это рекурсивная функция для вы-

числения суммы первых n элементов целочисленного массива "a[]".

int sum_of(int a[], int n)

{

if (n < 1 || n > MAXIMUM_NO_OF_ELEMENTS)

{

93

cout << "\nОшибка - допускается суммирование от 1 до ";

cout << MAXIMUM_NO_OF_ELEMENTS << " элементов\n";

exit(1);

}

else if (n == 1)

return a[0];

return a[n-1]+sum_of(a, n-1);

}

Фрагмент программы 4.3.

Рекурсия и циклы

При программировании на Си++ рекурсию применять совсем не обязательно, и

На практике она используется довольно редко. Любую рекурсивную функцию можно

реализовать итеративно, т.е. с помощью циклов "for", "while" или "do...while". В

Некотором смысле, какое определение функции выбрать (рекурсивное или итераци-

Онное) – вопрос пристрастий программиста. Исходный текст рекурсивных функций

Иногда легче для понимания, но итерационные функции практически всегда работают

Быстрее. Далее приводятся итерационные аналоги двух рекурсивных функций, кото-

Рые ранее рассматривались в данной лекции.

Void print_backwards()

{

char chars[MAX_ARRAY_LENGTH];

int no_of_chars_input = 0;

do {

cout << "Введите символ (или '.' для завершения ввода): ";

cin >> chars[no_of_chars_input];

no_of_chars_input++;

} while (chars[no_of_chars_input - 1]!= '.'

&& no_of_chars_input < MAX_ARRAY_LENGTH);

for (int count = no_of_chars_input - 2; count >= 0; count--)

cout << chars[count];

}

Фрагмент программы 2.1b.

Int factorial(int number)

{

int product = 1;

if (number < 0)

{

cout << "\nОшибка - отрицательный аргумент факториала\n";

exit(1);

}

else if (number == 0)

return 1;

for (; number > 0; number--)

product *= number;

return product;

}

94

Фрагмент программы 4.1b.

Конечно, вопрос о том, какая реализация конкретной функции более понятна,

Довольно спорный. Обычно в итерационную функцию приходится включать больше

Локальных переменных, например, в первом примере был добавлен массив

"chars[MAX_ARRAY_LENGTH]", а во втором примере – целочисленная переменная

"product". Другими словами, в итеративных функциях память тратится на локальные

Переменные, а рекурсивных функциях – на организацию вызовов.

Рекурсия в структурах данных

На практике рекурсия чаще встречается в структурных типах данных, а не в

Функциях. Рекурсивная структура данных уже использовалась в предыдущей лекции

для определения узла связного списка. В структуре "узел" хранится указатель струк-

туру такого же типа:

Struct node

{

char word[MAX_WORD_LENGTH];

node *ptr_to_next_node;

};

В последующих лекциях будут более подробно рассматриваться другие приме-

ры рекурсивных структур данных и особенности их описания на Си++.

Рекурсивная реализация алгоритма быстрой сортировки

В завершающей части этой лекции кратко рассмотрим известный пример ре-

курсивного алгоритма – алгоритм быстрой сортировки QuickSort. Этот рекурсивно

Определенный алгоритм предназначен для упорядочения значений, хранящихся в

Массиве, по возрастанию или по убыванию.

Предположим, что 11 элементов массива имеют следующие значения (рис. 6).

Рис. 6.. Начальное состояние массива.

Идея алгоритма основана на рекурсивном выполнении разбиения массива на

Две части и переупорядочении элементов в этих частях. Разбиение выполняется путем

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: