Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЛС_11,12.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
307.2 Кб
Скачать

5 Рекурсивні функції

Рекурсивною називається функція, яка викликає сама себе. Така рекурсія називається прямою. Існує ще непряма рекурсія, коли дві чи більше функцій викликають одна одну.

Несерйозні приклади, які добре ілюструють принцип рекурсії, можна знайти в дитячих лічилках, наприклад:

  1. попа був собака, він її любив Вона з'їла шматок м'яса, він її убив

  2. землю закопав,

Напис написав:

У попа був собака, він її любив Вона з'їла шматок м'яса, він її убив У землю закопав, Напис написав:

У попа був собака, він її любив Вона з'їла шматок м'яса, він її убив У землю закопав, Напис написав:

... (лапки цитат ніколи не закриються)

Загальновідомий приклад рекурсивного зображення – предмет між двома дзеркалами – у кожному з них видно нескінченний ряд відображень.

Окрім того, поняття рекурсії добре ілюструється матрьошками. Це означає, що рекурсія дозволяє звести складне завдання до розв’язку його зменшених копій. Послідовно зменшуючи розмір цих завдань, процес рекурсії в решті решт приведе до відомого розв’язку, використовуючи який можна легко здобути розв’язок початкового завдання.

Більш серйозні приклади рекурсії можна віднайти у математиці: рекурен-тні співвідношення визначають певний елемент послідовності через один чи кі-лька попередніх. Наприклад, числа Фібоначчі :

F(n) = F(n–1) + F(n–2), де F(0)=1, F(1)=1.

Якщо розглядати цю послідовність, розпочинаючи від молодших членів до старших, спосіб її побудови задається циклічним алгоритмом, а якщо, навпаки, – від заданого n=n0, то спосіб визначення цього елемента через попередні буде рекурсивним.

Вочевидь, що рекурсія не може бути безумовною, бо у такому разі вона стає нескінченною. Про це свідчить принаймні наведена вище лічилка. Рекурсія повинна мати всередині себе умову завершення, за якою наступний крок рекурсії вже не виконуватиметься.

Рекурсивні функції лише на перший погляд схожі на звичайні фрагменти програм. Щоб відчути специфіку рекурсивної функції, варто простежити за текстом програми перебіг її виконування. У звичайній програмі будемо йти ланцюжком викликів функцій , але жодного разу повторно не увійдемо до того самого фрагменту, допоки з нього не вийшли. Можна стверджувати, що перебіг виконування програми “лягає” однозначно на текст програми. Інша річ – рекурсія . Якщо спробувати відстежити за текстом програми перебіг її виконування, то дістанемось такої ситуації: увійшовши до рекурсивної функції, “рухаємося” її текстом доти , допоки не зустрінемо її виклик (можливо, з іншими параметрами), після чого знову розпочнемо виконувати ту ж саму функцію спочатку. При цьому слід зазначити найважливішу властивість рекурсивної функції: її перший виклик ще не завершився. Чисто зовні складається враження, що текст функції відтворюється (копіюється) щоразу, коли функція сама себе викликає. Насправді цей ефект відтворюється в комп’ютері. Однак копіюється при цьому не весь текст функції (не вся функція), а лише її частини, пов’язані з локальними дани-ми (формальні, фактичні параметри, локальні змінні й точка повертання). Алго-ритмічна частина (оператори, вирази) рекурсивної функції й глобальні змінні не змінюються, тому вони присутні в пам’яті комп’ютера в єдиному екземплярі.

Кожний рекурсивний виклик породжує новий “екземпляр ” формальних параметрів і локальних змінних, причому старий “екземпляр” не знищується, а зберігається у стеку на засаді вкладеності як “матрьошки”. Тут має місце єди-ний випадок , коли одному імені змінної в перебігу роботи програми відповідають кілька її екземплярів. Відбувається це в такій послідовності:

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

  1. при виклику функції до стека записується точка повертання – адреса тієї частини програми, де міститься виклик функції;

  1. на початку тіла функції у стеку резервується місце для локальних (автоматичних) змінних.

Зазначені змінні створюють групу (фрейм стека). Стек “пам’ятає історію” рекурсивних викликів у вигляді послідовності (ланцюжка) таких фреймів. Програма у кожний конкретний момент працює з останнім викликом і з останнім фреймом. По завершенні рекурсії програма повертається до попередньої версії рекурсивної функції й до попереднього фрейму у стеку.

Послідовність рекурсивних викликів можна прокоментувати приблизно такою фразою: “Функція F виконує ... і викликає F, яка виконує ... і викликає F...”.

Класичним прикладом рекурсивної функції є обчислення факторіала (це не означає, що факторіал треба обчислювати лише в такий спосіб). Для того щоб визначити факторіал числа n, слід помножити n на факторіал числа (n-1):

n! = n*(n-1)! // Це так зване рекурентне співвідношення.

Відомо також, що 0!=1 й 1!=1, тобто умовою зупинки рекурсії буде: якщо n=0 чи n=1, то факторіал дорівнює 1.

Дамо повне рекурсивне визначення факторіала:

n −1!⋅ n, n > 0;

n = 0.

n!=1,

Отже, рекурсивна функція обчислення факторіала числа n виглядатиме так:

long fact (long n)

{ if(n ==0 || n==1) return 1;

return (n*fact(n-1)); }

Якщо у функції main() зустрінеться виклик функції fact(3), при вико-нуванні цієї функції буде викликано функцію fact(2), яка своєю чергою викличе fact(1). Для останньої спрацює умова зупинки рекурсії (n==1) і у функцію fact(2) буде повернуто значення 1 та обчислено значення 2 (2! = 2), яке своєю чергою буде повернуто до функції fact(3) і буде використано для обчислення 3!.

Отже, програма має 3 рекурсивні виклики. Занурившись на три рівні ре-курсії, програма дісталася “глухого кута” – граничної точки, після чого вона зворотним ланцюжком має піднятися на ті самі три рівні. В результаті кожного рекурсивного виклику функція зберігає своє значення змінної n, оскільки до третього виклику у неї буде вже три окремих змінних з ім’ям n, при цьому кож-на має власне, відмінне від інших, значення, однак тіло функції буде присутнім в пам’яті в єдиному екземплярі. Цей ланцюжок рекурсивних викликів можна зобразити в такий спосіб:

Породження усіх нових копій рекурсивної функції до виходу на граничну умову називається рекурсивним спуском. Максимальна кількість копій рекурси-вної функції, яке водночас може міститися у пам’яті комп’ютера, називається глибиною рекурсії. У разі відсутності граничної умови необмежене зростання кількості таких копій призведе до аварійного завершення програми за рахунок переповнення стека. Завершення роботи рекурсивних функцій, аж до самої першої, яка ініціювала рекурсивні виклики, називається рекурсивним підйомом.

При створюванні рекурсивної функції завжди слід добре поміркувати, чи ефективно є застосовувати рекурсивний алгоритм, оскільки рекурсія з надто великою кількістю вкладених викликів може вичерпати ресурси оперативної пам’яті, що спричинить збій у роботі операційної системи.

Розглянемо алгоритм нераціонального використання рекурсії на прикладі обчислення чисел Фібоначчі – числового ряду, в якому кожен наступний член є сумою двох попередніх: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144 і т.д. Італійський математик середньовіччя Леонардо Фібоначчі відкрив цю числову послідов-ність, вивчаючи піраміду Хеопса у Гізі. До речі, ці числа пов’язані поміж собою цікавими співвідношеннями. Наприклад, кожне число є приблизно в 1.618 разів більше попереднього (цю пропорцію називають золотою), у 2.618 рази більше того, яке розташоване через одне число від нього і в 4.236 разів більше числа, розміщеного двома числами раніш, а кожне попереднє число складає приблиз-но 0.618 від наступного. Поширеність співвідношень Фібоначчі у житті просто вражає. Принцип золотого перетину – найвищий прояв структурної й функціо-нальної довершеності цілого та його частин у мистецтві, науці, техніці й природі. Закономірності “золотої” симетрії проявляються в енергетичних переходах елементарних часток, у будові деяких хімічних сполук , у планетарних і космічних системах, у генних структурах живих організмів, у принципах формотворення рослин тощо. Ці закономірності, виявлено як у будові окремих органів людини, так і тіла у цілому, а також вони проявляються у біоритмах і функціонуванні головного мозку і зорового сприйняття.

Ітераційна функція обчислення чисел Фібоначчі має вигляд

long fib1(int n)

{ long a=0, b=1, c=n; for(int i=2; i<=n; i++)

{ c=a+b; a=b; b=c; }

return c;

}

Рекурсивна функція матиме вигляд

long fib2(int n)

{ if(n==0||n==1) return n;

else return fib2(n-1)+fib2(n-2); }

або

long fib3(int n)

{ if(n>1) ? fib3(n-1)+fib3(n-2) : n; }

Така рекурсивна функція є придатна для використання лише для невели-ких значень n, оскільки кожне звертання призводить до двох подальших викли-ків, причому викликів з одним і тим самим значенням параметра, тобто кількість викликів зростає експоненціально. Отже, у цьому завданні доцільніше застосовувати ітераційну функцію fib1.

Рекурсивні функції найчастіше застосовують для компактної реалізації рекурсивних алгоритмів, а також для роботи зі структурами даних, описаними рекурсивно, наприклад з бінарними деревами. Кожну рекурсивну функцію можна реалізувати без застосування рекурсії. Достоїнством рекурсії є компактнийзапис, а недоліками – витрата часу й пам ’яті на повторні виклики функції й пе-редавання їй копій параметрів і, головне, небезпека переповнення стека.

Приклад 8.13 Увести ціле число і вивести всі цифри, які зображують це число.

Текст консольної програми:

void cnum (int n)

{ int a=10;

if(n

==

0) return;

cout << n%a<<endl; }

else

{

cnum(n/a);

}

void main() { int n;

cout << "Увести ціле число: ";

cin >> n; cnum(n);

getch(); return 0;

}

Результати виконання програми:

Увести ціле число: 1234 1 2 3 4

Приклад 8.14 Створити рекурсивну функцію піднесення числа до степеня. Аргументами цієї функції мають бути певне дійсне число, яке буде підноситись до степеня, і, безпосередньо, показник степеня цілого типу.

Тексти функції та її виклику в основній програмі:

#include <iostream.h> #include <conio.h>

double power(double x, int st)

{ if(st <= 0) return 1;

else return(x*power(x,st-1));

}

void main()

{ double x; int st;

cout<<"Увести число x= "; cin>>x; cout<<"Увести показник степеня st= "; cin>>st; cout<<"x ^ st= " << power(x, st);

getch(); return 0;

}

Результати виконання програми

Увести число x= 2.5

Увести показник степеня st= 2 x ^

st= 6.25

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