You are on page 1of 42

Алгоритми та структури даних

Лекція 1

Рекурсія.
Рекурсивні алгоритми

Лектор:
професор, доктор технічних наук
Мелешко Єлизавета Владиславівна
Рекурсія — процес повторення
чого-небудь самоподібним чином.
Наприклад, вкладені віддзеркалення,
вироблені двома точно паралельними
один одному дзеркалами, є однією з
форм нескінченної рекурсії.

В програмуванні та математиці
об'єкт називається рекурсивним, якщо
він містить сам себе або визначений за
допомогою самого себе.
Якщо процедура р містить явне звертання до самої себе, то вона
називається явно рекурсивною.
Процедура P

Якщо процедура р містить звертання до деякої процедури q, що у свою


чергу містить пряме або непряме звертання до р, то р - називається побічно
рекурсивною.
Процедура P Процедура Q

Але рекурсивна функція не може викликати себе нескінченно, інакше вона


ніколи не зупиниться.
У рекурсивній функції повинна бути умова, за якої програма припиняє
рекурсивний процес - термінальна умова.
Факторіал - добуток натуральних чисел від 1 до N.

N!=N(( N-1)!, для N>=1 та 0! = 1.

function factorial( N : integer ) : integer;


begin
if N=0 then // термінальна умова
factorial := 1
else
factorial := N * factorial( N-1); // програма викликає сама себе
//(з меншим значенням аргументу)
end;

Виклик factorial(-1) приведе до нескінченного рекурсивного


циклу. Тому перед викликом даної програми потрібно робити
перевірку умови незаперечності.
Числа Фібоначі - це елементи числової послідовності:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, …

у яких кожний наступний елемент дорівнює сумі попередніх.

FN = FN-1 + FN-2, де N >= 2 та F0 = F1 = 1.

function fibonacci( N : integer ) : integer;


begin
if N<=1 then
fibonacci := 1
else
fibonacci := fibonacci( N-1 ) + fibonacci( N-2 );
end;
Леонардо Пізанський (Фібоначчі) - математик європейського
середньовіччя - у «Книзі абака» сформулював таку задачу:

«Хтось помістив пару кроликів в деякому місці, огородженому з усіх


боків стіною, щоб дізнатися, скільки пар кроликів народиться при цьому
протягом року, якщо природа кроликів така, що через місяць пара
кроликів приводить на світ іншу пару, а народжують кролики з другого
місяця після свого народження».
Нерекурсивна (ітераційна) реалізація обчислення чисел Фібоначі:

const
max = 25;
var
i : integer;
F : array [0..max] of integer;

procedure fibonacci;
Begin
F[0] := 1;
F[1] := 1;
for i := 2 to max do
F[i] := F[ i-1]+F[ i-2];
end;
Точних правил для вибору між рекурсивною й нерекурсивною версіями
алгоритму рішення завдання не існує.
Стислість і виразність більшості рекурсивних процедур спрощує їхнє читання й
супровід.
Але виконання рекурсивних процедур вимагає згачно більше витрат пам'яті і
часу процесора.
Ханойські вежі. Цю задачу придумав
Едуард Люка в XIX столітті.

Є три стрижня - лівий, середній і правий.

На лівому стержні знаходяться n дисків,


діаметри яких різні.

Диски впорядковані за розміром


діаметра, зверху лежить найменший,
знизу - найбільший.

Потрібно перенести диски з лівого


стрижня на правий, використовуючи
середній стрижень як допоміжний.

Переносити можна тільки по одному


диску, при цьому не можна класти диск
більшого діаметра на диск меншого
діаметру.
Очевидно, що для того щоб перенести нижній диск на правий стрижень,
потрібно попередньо перенести вежу, що складається з n - 1 дисків, з лівого
стрижня на середній стрижень (b). Після того, як нижній диск виявиться на
правому стержні (c), залишиться перенести вежу з середнього стрижня на
правий стрижень (d). Неважко показати, що оптимальне рішення задачі з n
дисками складається з 2n - 1 переміщень дисків.

hanoi(N, A, B, C):

// з A на B через С
hanoi(N - 1, A, C, B),

// з B на C через A
hanoi(N - 1, B, A, C).

Непряма (побічна) рекурсія

Процедура А викликає процедуру В, а та, в свою чергу, -


процедуру А.

Довжина таких ланцюжків викликів може бути довільною,


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

Приклад. Процедури Rec1 і Rec2 рекурсивно викликають


одна одну, по черзі зменшуючи свої фактичні параметри.
Обидві процедури працюють з однією глобальною змінною
А, яка передається в них за посиланням. Критерієм
завершення роботи є значення цієї змінної нуль.
Program KosvRecurs;
Var
A : integer;
Procedure Rec2 (Var Y:integer); Forward;

Procedure Rec1 (Var X:integer);


Begin
X := X-1;
if X>0
then
Rec2;
writeln (X)
End;

Procedure Rec2 (Var Y:integer);


Begin
Y := Y div 2;
if Y>2
then
Rec1;
writeln (Y)
End;

Begin
A := 15;
Rec1(A);
End.
Розподіл навпіл – алгоритм, що використовує 2 рекурсивні виклики, кожний з
яких працює приблизно з половиною вхідних даних; його часто використовують
для досягнення істотної економії ресурсів.
Приклад. Нанесення розподілів на лінійку: на ній повинна бути мітка в точці
1/2", мітка трохи коротша через кожні 1/4", ще більш коротка через 1/8" і так далі.

Припустимо, що є процедура mark(x, h) для нанесення мітки висотою h


одиниць на лінійку в позицію x. Висота центральної мітки повинна бути - n, мітки в
центрах лівої й правої половинок n-1 і т.д.
procedure rule( l, r, h : integer );
var m : integer;
begin
if h>0 then
begin
m := (l+r) div 2;
mark( m, h );
rule( l, m, h-1 );
rule( m, r, h-1 );
end; end;
Розглянемо приклад виклику rule(0, 8, 3).
Ми ставимо мітку по середині й викликаємо rule для лівої половини, потім
робимо теж саме для лівої половини, і так далі поки висота мітки не стане
дорівнювати 0.
В остаточному підсумку ми вертаємося з rule і розмічаємо праву половину
подібним чином.
1 Rule
(0,8,3)

2
Rule 9 Rule
(0,4,2) (4,8,2)

3 10
6 13
Rule Rule Rule Rule
(0,2,1) (2,4,1) (4,6,1) (6,8,1)

4 7 8 11 12 15 16
5
Rule Rule Rule Rule Rule Rule Rule Rule
(0,1,0) (1,2,0) (2,3,0) (3,4,0) (4,5,0) (5,6,0) (6,7,0) (7,0,0)
Реалізація рекурсивних викликів функцій у практично використовуваних
мовах і середовищах програмування, як правило, опирається на механізм
стека викликів – адреси повернення й локальних змінних функцій
записуються в стек, завдяки чому кожний наступний рекурсивний виклик цієї
функції користується своїм набором локальних змінних і за рахунок цього
працює коректно.

Недолік: на кожний рекурсивний виклик потрібна деяка кількість


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

Такі рекурсивні обчислення, навіть якщо вони формально нескінченні,


ніколи не приводять до вичерпання пам'яті.

Однак, далеко не завжди стандарти мов програмування чітко


визначають, яким саме умовам повинна задовольняти рекурсивна
функція, щоб транслятор гарантовано перетворив її в ітерацію. Один з
рідкісних винятків – мова Scheme (діалект мови Lisp), опис якої містить всі
необхідні відомості.

Будь-яку рекурсивну функцію можна замінити циклом або стеком.


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

Стохастичний варіант Детермінований фрактал та реальне


трикутника Серпінського дерево як приклад стохастичного фракталу
Мауріц Корнеліс Ешер
нідерландський художник-графік

Малюнки з фракталами та рекурсіями


Алгоритм (латинізов. Algorithmi, від імені перського математика IX ст.
аль-Хорезмі) – опис послідовного скінченного набору кроків (операцій),
виконання якого обов'язково призводить до розв'язання певного класу
задач.

Визначення алгоритму

Неформальні Формальні
(математичні моделі)
Словесний опис
властивостей - Машина Тюрінга
- Нормальный алгоритм Маркова
- тощо
Способи запису алгоритмів

Графічний Мовний

- блок-схеми; - звичайна мова


- функціональні схеми; (напр., як кулінарний рецепт)
- UML; - псевдокод
- тощо.
Псевдокод – компактна мова опису алгоритмів, що використовує ключові слова
структурних мов програмування, але опускає несуттєві подробиці й специфічний
синтаксис.

Несуттєвими деталями можуть бути опис змінних, системно-залежний код і


підпрограми.

Псевдокод використовується в підручниках, наукових публікаціях, на початкових


стадіях розробки програмного забезпечення.

Назва структури Псевдокод (приклад)

присвоювання, введення, виведення змінна = 0, введення х, виведення х

якщо х=1 то
розгалуження y:=2
інакше y:=3
поки х<10
початок
цикл поки y:= y+1
z:= z*2
кінець
Загальний вид алгоритму:

алг назва алгоритму (аргументи й результати)


дано умови застосовності алгоритму
треба ціль виконання алгоритму
початок опис проміжних величин
| послідовність команд (тіло алгоритму)
кінець

алг Сума квадратів (арг цілий n, рез цілий S)


дано | n > 0
треба | S = 1*1 + 2*2 + 3*3 + ... + n*n
поч цілий i

введення n; S:=0
пч для i від 1 до n
S:=S+i*i
кц
вивід "S = ", S

кін
Неформальне визначення алгоритму

Кожен алгоритм передбачає існування:

- вхідних даних
- вихідних даних (результату).

Кожен алгоритм має властивості:

- дискретність - послідовність деяких елементарних дій (кроків)


- масовість - можливість застосування до різних вхідних даних для
однотипних задач.
- детермінованість - виконання команд алгоритму призводить до однакового
результату для однакових вхідних даних. (виняток - стохастичні алгоритми,
нариклад, генератор випадкових чисел).
- сукупність припустимих вхідних даних - вхідні дані алгоритму можуть бути
обмежені певним набором.
Формальне визначення алгоритму. Машина Тюринга
Машина Тюрінга - це абстрактна машина, що працює з безкінечною стрічкою
окремих комірок, в яких записано певні символи, дана машина має голівку для
читання/запису, що може рухатися вздовж стрічки та управляючий пристрій, з
записаною у ньому програмою.

Машина Тюринга складається з:


- Алфавіту. Вхідна і вихідна мови.
- Станів. Початковий і кінцевий стан.
-Функцій руху, читання, запису, зупинки.
Приклад. Перевірка на парність кількості одиниць, записаних на стрічці:

* * * 1 1 1 1 1 * *

Програма для машини Тюрінга


q1 1 –> q2 R q0
парна k непарна k
q2 1 –> q1 R
* *
q1 * –> q0 S парна k 1
q2 * –> q0 S непарна k q1 q2
1
На основі дослідження цих машин було висунуто тезу Тюрінга (основна гіпотеза
алгоритмів):

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

Алгоритмічно нерозв'язні задачі

Функцію f називають обчислюваною, якщо існує машина Тюрінга, яка обчислює


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

Тому важливо точно вказувати припустиму множину вхідних даних, оскільки задача
може бути розв'язною для однієї множини та нерозв'язною для іншої.

Однією з перших задач, для якої було доведено нерозв'язність є проблема зупинки.
Формулюється вона наступним чином:
Маючи опис програми для машини Тюринга, визначити, чи завершить роботу
програма за скінченний час, чи працюватиме нескінченно, отримавши будь-які вхідні
дані.
Алгоритмічно нерозв’язні задачі
• Проблема зупинки
• Проблема самозастосованості

Назвемо Аналізатором гіпотетичний алгоритм, який отримує на вхід пару


натуральних чисел (N, X), і:

- зупиняється та повертає 1, якщо інший алгоритм з номером N не


зупиняється, отримавши на вхід X.

- не зупиняється в іншому випадку (якщо інший алгоритм з номером N


зупиняється, отримавши на вхід X).

Якщо застосувати алгоритм до самого себе, то він не зможе працювати,


тому що повинен буде зупинитися тоді, коли він сам не зупиняється, і не
зупинятися в тому випадку, якщо він сам зупиниться.
Алгоритмічно нерозв’язні задачі
• Пошук досконалих чисел
Досконалі числа - це такі числа, сума всіх дільників яких дорівнює самому
числу. Наприклад :
6 ділиться на 1, 2 і 3; 1 + 2 + 3 = 6. 6 - досконале число. 28 теж досконале число.
Використовуються у криптографії. На їх пошуки кинуті колосальні
обчислювальні потужності, але поки не відомо:
- скільки цих чисел,
- обмежена чи ні їх кількість
- чи існують непарні досконалі числа.

Шукати досконалі числа можна різними способами, але всі вони зводяться до
перебору. Просто можна перебирати не всі підряд числа.

Якщо б у нас була чарівна функція F, ми могли б їй «згодувати» наш нехитрий


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

1. Ми малюємо трикутник
2. На наступному кроці на кожній його стороні малюємо ще по трикутнику
3. На наступному - на кожному з отриманих трикутників ще по трикутнику
І т.д.

Проблема: якщо ми поставимо поруч з такою сніжинкою точку, то ми не


можемо визначити, чи потрапить ця точка в сніжинку, коли та буде
розростатися, на певному етапі реалізації цього алгоритму. Якщо точка не
потрапить в сніжинку на 100 кроці, ми не знаємо, чи станеться це на 150 або
на 233 кроці - алгоритм нескінченний.
Дякую за увагу!

You might also like