Рекурсія. h2>
З поняттям рекурсії ми вже зустрічалися: рекурентні співвідношення досить
часто зустрічаються в математичних виразах. Рекурсія у визначенні полягає в
те, що визначається поняття визначається через саме це поняття. Прикладом
тут може служити визначення висловлювання (див. лекція 5, визначення 5.1).
Рекурсія в обчисленнях виступає у формі рекурентних співвідношень, які
показують, як обчислити чергове значення, використовуючи попередні. p>
Наприклад, рекурентні співвідношення p>
xi = xi-2 + xi-1,
де x1 = 1, x2 = 2 p>
задає правило обчислення так званих чисел Фібоначчі. p>
Іншим прикладом рекурентних
співвідношень можуть служити правила обчислення членів арифметичної прогресії p>
an 1 = an + d
, Де d - різниця
прогресії, p>
або геометричній прогресії p>
an +1 = q an,
де q - коефіцієнт прогресії. p>
Ця ідея рекурсії реалізована і в мові Pascal. p>
Визначення 16.1. Функція (процедура) на мові Pascal називається рекурсивної, якщо в ході свого
виконання вона звертається до самої себе. p>
Наприклад, ми можемо визначити обчислення функції n!
рекурсивно. Як це зробити, показано на малюнку 16.1 p>
function Factorial (n: integer): integer; p>
begin if
n> 0 then Factorial: = Factorial
(n-1) * n p>
else if n = 0 then Factorial: = 1 p>
else writeln ( 'значення n менше 0') p>
end
(Factorial) p>
Рис. 16.1. Функція обчислення n! в рекурсивної формі. p>
Розглянемо детальніше, як буде виконуватися звернення до цієї функції,
напрмер, при n = 4. p>
На малюнку 16.2 показаний процес обчислення для випадку Factorial (4). p>
24 b> p>
p>
Рис. 16.2. Обчислення функції Factorial (n) для n = 4. P>
Спочатку утворюється так званий рекурсивний фрейм № 1 при n = 4. Для цього фрейму відводиться пам'ять
і в ньому фіксуються всі значення змінних тіла функції при n = 4. Відзначимо, що в рекурсивної фреймі
фіксуються значення всіх змінних функції, окрім глобальних. p>
Потім відбувається виклик Factorial (n) при n = 3. Утворюється фрейм № 2, де фіксуються
значення змінних тіла функції при n = 3. При цьому фрейм № 1 також зберігається в пам'яті. З фрейма
№ 2 відбувається звернення до Factorial (n) при n = 2. У результаті
цього звернення утворюється фрейм № 3, де фіксуються значення змінних тіла
функції при n = 2 і т.д. до тих пір,
поки при черговому зверненні до функції Factorial умова n> 0 не прийме значення false. p>
Це відбудеться в фреймі № 5. У цьому
фреймі ми отримаємо значення Factorial = 1 і передамо це значення в фрейм № 4. Після цього
фрейм № 5 буде знищений, так як звернення Factorial (n) при n = 0 буде виконано. p>
У фреймі № 4 ми обчислимо значення Factorial (n) для n = 1. Після чого ми передамо це значення у
фрейм № 3, а фрейм № 4 буде закрито, оскільки звернення до Factorial (n) при n = 1 буде закінчено. p>
Так ми будемо згортати цю
ланцюжок фреймів в послідовності, зворотній тій, в якій ми їх породжували,
поки не звернемо фрейм № 1. Після чого обчислення функції буде закінчено. P>
Рекурсія можлива не тільки в
випадку функцій, але і процедур. Приклад рекурсії для процедур наведено на малюнку
16.3. Там показано опис рекурсивної процедури для роздруківки (виведення на
друк) рядка символів в порядку, зворотному їх введенню. p>
Procedure BackPrint; p>
var символ: char; p>
begin
read (символ); p>
if
символ = EOL (EOL - End Of Line - спеціальне
значення типу p>
СHAR, відповідне
закінчення введення) p>
then writeln (); (перед початком виведення треба переконатися,
що p>
друкувати будемо з нового рядка) p>
else
begin BackPrint; write (символ) end p>
end
(Procedure) p>
Рис 16.3. Приклад рекурсивної
процедури. p>
(Непряма рекурсія.) Ітерація і рекурсія. h2>
Неважко помітити схожість між
циклічними конструкціями (повтореннями) і рекурсією. На малюнку показана 16.4
схема циклу виду while do і його рекурсивного
аналога. p>
Цикл p>
Рекурсія p>
while Умова Циклу p>
do Тіло Циклу p>
Procedure Рекурсивний Цикл; p>
begin p>
if Умова Циклу p>
then
begin Тіло Циклу; p>
Рекурсивний Цикл p>
else (закінчення
рекурсії) p>
end p>
Рис. 16.4. Схема організації циклу виду while do p>
і його рекурсивного еквівалента. p>
Зверніть увагу, що в правій частині рис. 16.4 можливо зациклення! Треба
бути дуже обережним і кожного разу, застосовуючи рекурсивну поцедуру або функцію,
переконатися в їх коректне завершення. Розглянемо приклад. На малюнку 16.5
наведено алгоритм Евкліда, з яким ми познайомилися на лекції 1, для
обчислення НОД (найбільшого загального дільника) у формі звичайної і рекурсивної
функції на мові Pascal. p>
Function
НОД (a, b: integer): integer; p>
begin repeat p>
if a> b then a: = a-b p>
else b: = b-a p>
untile a = b; p>
НОД: = a p>
end p>
begin if a = b then НОД: = a; p>
if a> b then НОД: = НОД (ab, b); p>
else НОД: = НОД (ba, a); p>
end p>
Рис. 16.5. Циклічна і рекурсивна функції p>
для обчислення НОД. p>
Як видно з наведених прикладів на рисунках 16.1 і 16.5, ітерація, тобто
цикл завжди може бути замінений його рекурсивним аналогом за схемою, наведеної на
малюнку 16.4. p>
З зворотним твердженням про заміну
рекурсії итерацией все складніше. На малюнку 16.6 наведено приклад рекурсивної
функції, де за схемою (рис. 16.4) рекурсію итерацией замінити не вдається. p>
в інших випадках p>
p>
Рис. 16.6. Рекурсивна функція Аккермана. P>
Способи повторного використання процедур і функцій. p>
Отже, процес абстракції у формі процедури складається з трьох кроків: p>
Іменування. Присвоїти рутинному алгоритму унікальне ім'я, яке потім
будемо використовувати як ім'я відповідної процедури. p>
Визначити перед-і постусловія для створюваної процедури або функції в
відповідно до контексту їх використання в основній програмі. p>
Параметрізіовать процедуру. (Скрізь далі, якщо явно не зазначено, говорячи про
процедурах, будемо мати на увазі також і функції). Для цього частина передумови і
постусловія в специфікації оформити у вигляді параметрів відповідного типу,
частина з яких буде доставляти вихідні дані, а інша частина - результати
роботи процедури. p>
узагальнити типи параметрів. Проаналізувати всі місця в програмі, де буде
звернення до даної процедури на предмет, які типи даних використовуються в цих
місцях, як вони співвідносяться з типами параметрів у процедурі. Назвемо
сукупність типів даних в місці виклику процедури контекстом звернення до
процедурі Визначити типи параметрів так, щоб вони відповідали як можна
більшій кількості контекстів звернень до процедури. p>
Реалізувати вийшла абстракцію рутинного алгоритму або у формі
процедури, або функції. p>
Ми не в праві чекати, що виділені нами вже існуючі функції або
процедури, які можуть бути нам корисні для створення нашої нової програми,
ми зможемо використовувати в тому вигляді, як вони є. Є чотири основні способи
адаптації або повторного використання вже існуючих рутинних алгоритмів і
процедур для нових цілей. Це - приєднання, вкладення, настройка і злиття. P>
Приєднання. Цей спосіб передбачає, що якщо у нас є процедура P1 c передумовою Q1 і постусловіем R1 і процедура P2 c перед-і c постусловіямі Q2 і R2 відповідно,
(причому R1