Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Visual_C_console.pdf
Скачиваний:
34
Добавлен:
16.05.2015
Размер:
954.14 Кб
Скачать

157

Рекурсия

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

Существенным отличием рекурсии от итеративных и циклических процессов, реализуемых с помощью оператора цикла, является формирование стека, в который погружаются значения всех локальных переменных, объявленных в рекурсивной функции. По этой причине использование рекурсии оправдано в тех случаях, когда задача проще решается с использованием стека. Во многих учебниках по программированию приводится пример рекурсивной программы для вычисления факториала, который не объясняет рекурсию, поскольку для решения данной задачи стек не нужен. Ниже приведены примеры задач, в которых применение стека упрощает решение.

Пример 1. Вводится целое число. Требуется распечатать цифры этого числа по одной в строке (распечатать число по вертикали).

Естественный алгоритм решения этой задачи следующий:

1.Вычислить остаток от деления исходного (целого) числа на 10 (этот остаток является самой младшей цифрой исходного числа).

2.Сохранить полученную цифру.

3.Отбросить младшую цифру числа (это можно выполнить путем деления числа на 10, поскольку при целочисленном делении дробная часть частного отбрасывается без округления).

4.Если после отбрасывания очередной цифры оставшаяся часть числа не равна нулю, повторить п.1.

5.Если после отбрасывания очередной цифры оставшаяся часть числа равна нулю, печатать цифру из стека и возврат к предыдущему вызову рекурсивной подпрограммы. Повторять п.5 до возврата в главную программу.

158

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

Схематически процесс вывода числа, состоящего из трех цифр можно представить в следующем виде:

Ниже приведен полный текст этой программы

// Печать цифр числа по вертикали сверху вниз.

#include "stdafx.h" #include <string> #include <conio.h> #include <iostream> using namespace std; #include <iomanip> #define MAX 2147483647L int i = 0; // Счетчик цифр

long num; // Цифры этого числа будут выводиться по вертикали void number_digit (void); // Прототип рекурсивной функции

159

void _tmain ()

{

cout << "Введите число\n"; cin >> num;

// Вход в рекурсию

if ((num >= 0) && (num < MAX)) number_digit();

else cout << "Число вне допускаемого диапазона"; _getch ();

}

// Рекурсивная функция void number_digit(void)

{

int d;

// Локальная переменная, реализующая стек

// Остаток от деления на 10 - последняя цифра числа

d = num % 10;

 

num /= 10;

// Отбросить последнюю цифру

if (num > 0) number_digit();// Цифры еще есть рекурсия cout << "\nЦифра " << ++i << " равна " << d; // Печать стека return; // Возврат к предыдущему вызову

}

Впрограмме для вводимого числа выбран тип данных long, чтобы увеличить диапазон чисел, обрабатываемых программой до 10 цифр.

Восновной программе вводится число и проверяется его принадлежность допустимому диапазону значений. Если число может быть обработано, вызывается рекурсивная функция number_digit, которая и реализует алгоритм.

Впрограмме number_digit объявлена переменная d для хранения выделяемых цифр числа. Следующие далее две строки реализуют описанный выше алгоритм выделения очередной цифры. Затем следует проверка. Если после отбрасывания очередной цифры нуль не получен, то снова вызывается функция number_digit ().

160

При каждом вызове любой функции для всех объявленных в ней локальных переменных, память выделяется автоматически, а выделенная для хранения этих переменных память освобождается только после возврата из функции.

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

Если при очередном вызове функции number_digit переменная num становится равной нулю, это значит, что переменная d хранит теперь последнюю из оставшихся цифр, а в действительности первую цифру, поскольку после получения этой цифры она была отброшена, и переменная num стала равной нулю. Теперь вложенных вызовов функции number_digit больше не происходит, а, следовательно, следующим будет выполняться оператор, записанный после оператора if. В данной функции это оператор вывода на экран значения переменной d. (В рекурсивных подпрограммах записывать

161

оператор else после if не следует, поскольку чаще всего это приведет е неправильной работе подпрограммы).

Как было указано, первый раз операция cout<< будет выполнена, когда значение переменной num станет равным нулю, а это произойдет, когда будет получена первая цифра числа. Записанный после операции cout<< оператор return выполняет возврат из текущего вызова функции number_digit, что в свою очередь означает выполнение оператора, следующего за оператором if в предыдущем вызове функции number_digit. Но при предыдущем вызове была вычислена предпоследняя цифра числа num, которая и будет распечатана. Аналогично продолжается последовательный возврат к предыдущим вызовам функции number_digit до тех пор, пока не будет распечатана последняя цифра числа. После этого будет выполнен окончательный возврат в основную программу.

Примечание: При распечатке цифр числа в приведенной программе сначала номер цифры наращивается, а потом распечатывается.

На примере функции number_digit можно описать типовые элементы рекурсивной функции:

1.Заголовок функции, который содержит тип возвращаемого значения, имя функции и перечень передаваемых в функцию параметров.

2.Объявление локальных переменных. Значения именно этих переменных будут погружаться в стек.

3.Операторы, задающие безусловные действия, выполняемые при каждом вызове рекурсивной функции. Эти операторы могут быть записаны либо перед проверкой условия окончания рекурсивных вызовов, либо после проверки этого условия (перед оператором return), т.е. перед извлечением предыдущего стека.

4.Условие продолжения рекурсивных вызовов. В функции обязательно должен изменяться некоторый параметр, и рекурсивный вызов прекращается, когда этот параметр достигнет указанного в условии значения (0, NULL и т.д.).

162

5.Стек может быть организован посредством локальных переменных, как указано в приведенном тексте функции, но может формироваться и из последовательных значений аргументов функции.

6.Действия, выполняемые перед возвратом из функции (выгрузка стека).

7.Возврат из функции. Если функции не возвращает значения через свое имя, то оператор return не является обязательным.

Для перечисленных элементов рекурсивной функции при реализации конкретных алгоритмов может несколько изменяться форма записи или, в некоторых случаях, порядок их записи. Так, в функции вместо вывода на экран может выполняться передача информации в память функции, вызвавшей рекурсию.

Главное при оформлении рекурсивной функции это правильное задание условия выхода из рекурсивных вызовов. Если при зацикливании программы на операторе цикла машина может бесконечно выполнять такой цикл, не реагируя на любые действия пользователя, то при зацикливании из-за неправильного оформления рекурсивной функции неизбежно происходит аварийный останов, когда будет исчерпан объем оперативной памяти, которая выделяется при каждом рекурсивном вызове для локальных переменных.

Пример 2. Задан набор номиналов (достоинств монет). Вводится денежная сумма. Разменять эту сумму при минимальном количестве монет.

// Размен денег при минимальном количестве монет

#include "stdafx.h" #include <string> #include <conio.h> #include <iostream> #include <iomanip> using namespace std;

void exchange(int sum, int i); // Прототип рекурсивной функции

#define HOW_MANY_COINS 6 // Количество номиналов монет

163

int coins[] ={1, 2, 5, 10, 20, 50}; // Номинальные значение монет void _tmain()

{

int sum;

cout << "Введите сумму\n"; cin >> sum;

cout << "Введена сумма " << sum << " размен такой: \n"; exchange(sum, HOW_MANY_COINS - 1);

/* Индекс последней монеты на 1 меньше количества номиналов монет */

_getch();

}

void exchange(int sum, int i)

{

int h = 0; // Стек (счетчик монет данного достоинства) while(sum >= coins[i]) // Пока сумма больше цены монеты

{

sum = sum - coins[i]; // Вычитание достоинства монеты h++; // Нарастить счетчик

}

if(sum>0)exchange(sum,i-1);// Рекурсивный вызов (сумма не 0) if(h > 0) cout << "\nМонет достоинством " << setw(3) <<

coins[i]<< " требуется " << h;

}

Вэтой программе реализован следующий алгоритм:

Впрограмме задан глобальный массив достоинств (номиналов) монет. После ввода суммы для размена вызывается рекурсивная процедура, в которую передается значение суммы для размена и максимальный индекс массива с номиналами монет.

Врекурсивной процедуре объявлена локальная переменная h, которая хранит значение счетчика количества монет данного достоинства. Стоимость монеты задается значением в массиве номиналов монет. Монете с максимальным достоинством соответствует эле-

164

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

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

Рекурсивные вызовы продолжаются до тех, пока остаток суммы больше нуля. При каждом вызове значение счетчика монет погружается в стек. Зацикливание программы не произойдет потому, что значение суммы для размена выражено целым числом, а минимальное достоинство монет 1. Когда значение остатка суммы станет равным нулю, начнется выгрузка стека. Распечатка достоинства монеты и количества таких монет при выгрузке стека выполняется при значении счетчика большем нуля.

Пример 3. Рекурсия с множественными рекурсивными вызовами.

В одной рекурсивной функции может быть несколько рекурсивных вызовов. Рассмотрим пример такой программы. Пусть задана матрица, содержащая целочисленные нули и единицы. Цепочкой является любая последовательность единиц (от 1 до любого количества), у которой в любой смежной строке или смежном столбце имеется единица. Ниже в примере программы имеется 7 цепочек.

Для решения задачи напишем рекурсивную функцию, которая текущую единицу заменяет значением счетчика, и рекурсивно вызывает себя для анализа содержимого матрицы в смежных строках и столбцах (с проверкой на выход за пределы матрицы). В главной программе откроем два цикла для просмотра содержимого матрица. Если очередная ячейка матрицы содержит единицу, то наращиваем

165

значение счетчика (count, начальное значение счетчика 2) и вызываем рекурсивную процедуру. Таким образом, единицы, составляющие цепочку, какую бы форму они не имели, будут заменены значением счетчика. В соответствие с этим алгоритмом единицы в первой цепочке будут заменены двойками, во второй тройками и т. д. По окончании поиска единиц в матрице значение счетчика будет на 1 больше количества цепочек.

// Подсчет количества цепочек в матрице

#include "stdafx.h" #include <conio.h> #include <iostream> using namespace std; #define SIZE 10

int f[SIZE][SIZE] = {{0,1,0,0,0,0,0,0,0,0}, {0,0,1,1,1,0,0,0,0,0}, {0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,0,1,0,0,0,0}, {0,0,1,1,1,0,0,0,0,0}, {0,0,0,0,1,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,1,1,1,0,1,1,1,1}, {0,0,1,0,1,0,0,0,0,0}, {0,0,1,0,1,0,0,0,1,0}};

int count = 1; // count – количество цепочек + 1 void chseek (int n, int m);

int _tmain ()

{

for (int i = 0; i < SIZE; i++)

for (int j = 0; j < SIZE; j++) { if (f[i][j] == 1)

{

count++; f[i][j] = count; chseek (i, j);

 

166

 

}

 

}

 

cout << "В матрице " << (count - 1) << " цепочек";

 

_getch();

 

}

/*

n - номер строки

 

m - номер столбца */

// Рекурсивная функция подсчета количества цепочек. void chseek(int n, int m)

{

 

 

if (n < SIZE && f[n + 1][m] == 1) {

// 1

в строке ниже текущей

f[n + 1][m] = count;

 

 

chseek(n + 1, m);

 

 

}

 

 

if (m < SIZE && f[n][m + 1] == 1) {

// 1

в столбце правее текущего

f[n][m + 1] = count;

 

 

chseek(n, m + 1);

 

 

}

 

 

if (n > 0 && f[n - 1][m] == 1) {

// 1

в строке выше текущей

f[n - 1][m] = count;

 

 

chseek(n - 1, m);

 

 

}

if (m > 0 && f[n][m - 1] == 1) { // 1 в столбце левее текущего f[n][m - 1] = count;

chseek(n, m - 1);

}

}

Вданном примере результат:

Вматрице 7 цепочек

При усложнении проверок с помощью этого алгоритма можно подсчитать количество замкнутых контуров, состоящих из единиц, и т. д.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]