Рекурсивные структуры данных — это те структуры данных, которые содержат сами себя в собственных определениях. Но сегодня мы сосредоточимся на рекурсивных функциях.
Итак. Практически все функции принимают на вход некоторые данные и возвращают что-то на выходе.
На рисунке параллелепипед — тело функции, то есть набор неких инструкций, которые интерпретируют исходные данные и формируют выходные.
Очень часто при рассмотрении тела функций оказывается, что они вызывают другие функции.
Вот, например, эта простейшая функция foo. Она принимает на вход строку, подсчитывает количество символов в ней и выводит на экран это число.
Чтобы посчитать длину строки, функция вызывает другую функцию — strlen.
Данные, которые она генерирует на выходе, нужны, чтобы вывести их на экран с помощью printf.
Особенность рекурсивной функции в том, что она вызывает сама себя. Обозначим этот процесс на картинке оранжевой стрелкой. Однако вызов самой себя предполагает ещё один рекурсивный вызов… А затем ещё один, и ещё один!
Но не может же это длиться вечно? Рекурсивная функция, как и любая другая, должна в конце концов выдать результат и закончить работу. Итак, наша ближайшая задача — найти способ выхода из рекурсивной функции.
Когда выполняется условие выхода из рекурсии, функция завершается без рекурсивного вызова.
Возьмем функцию hi. Она ничего не возвращает, но принимает на вход целое число n.
void hi(int n)
{
if (n <= 0)
{
printf("Bye!\n");
return;
}
printf("Hi!\n");
hi(n-1);
}
Выход из рекурсии идёт первым. Если n <= 0 мы просто выводим «Bye!» и выходим из функции. Для всех других случаев функция будет выводить «Hi!» и делать рекурсивный вызов — то есть ещё один вызов функции hi с уменьшенным на единицу значением n. И так будет продолжаться до тех пор, пока n не станет равно нулю. Тогда функция напишет “Bye!” и закончит свою работу.
Поскольку hi ничего не возвращает, мы можем не писать return явным образом.
Теперь давайте рассмотрим пример поинтереснее, в котором функция будет возвращать явный результат. Давайте запрограммируем функцию факториала, которая очень часто используется в подсчётах вероятностей и комбинаторике.
Факториалом целого положительного числа n называется произведение всех целых положительных чисел, которые меньше или равны n.
То есть
5! = 5*4*3*2*1 = 120
4! = 4*3*2*1 = 24
3! = 3*2*1 = 6
2! = 2*1 = 2
1! = 1
И да, по определению, 0! = 1.
Как можно написать функцию, которая рекурсивно подсчитывает факториал числа? Для начала нам нужно определить выход из рекурсии и рекурсивный вызов.
Рекурсивный вызов будет одинаков для всех случаев, кроме выхода. Это значит, нам нужно найти шаблон, который поможет нам решить задачу. На картинке выше видно, что 5! — это 4!*5, 4! — это 3!*4 и так далее, вплоть до 1!, который равен 1.
Таким образом, наш шаблон для нашего рекурсивного вызова:
n! = (n-1)! * n
А наш выход из рекурсии или базис рекурсии будет определение факториала для единицы.
Теперь напишем код для подсчёта факториала. Создадим функцию int factorial (int n). Для выхода из рекурсии нам нужно, чтобы выполнилось условие n == 1. В таком случае мы возвращаем 1. Потом, двигаясь по рекурсивному вызову мы будем возвращать n*factorial(n-1).
int factorial(int n)
{
// base case
if (n == 1)
{
return 1;
}
// recursive case
return n * factorial(n - 1);
}
Протестируем программу. Попробуем найти 4! Для нашей функции это 4*factorial(3), а factorial(3) = factorial(2)*3, factorial(2) = factorial(1) *2. Ну а factorial(1) в свою очередь равен 1.
Мы получили эту единицу, и теперь последовательно возвращаем 2, 2*3 = 6, 6*4 = 24.
На первых этапах знакомства с рекурсией могут быть проблемы с пониманием этого алгоритма. Важно понять, что она как бы «раскручивается» до базиса рекурсии, и затем вновь «закручивается». То есть, если нам нужно определить 5!, считайте, что 4! у нас уже есть.
Задание
Чтобы убедиться, что вы хорошо поняли рекурсию, попробуйте выполнить следующее задание:
Создать рекурсивную функцию sigma, которая считывает сумму натуральных чисел от 0 до заданного на входе числа n.
int sigma (int n);
В консоли должно отобразиться что-то в этом роде:
jharvard@run.cs50.net (~): ./a.out
Enter a positive integer: 5
15
Ну а если вы чувствуете себя совсем уверенно, попробуйте создать рекурсивную функцию бинарного поиска (помните разорванную Дэвидом книгу?).
Давайте поищем число n в заданном массиве.
bool search(int n, int array[], int lower, int upper);
Она должна возвращать true, если число n найдено, и false, если нет.
jharvard@run.cs50.net (~): ./a.out
> 4
YES
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ