2. Съдържание 1/2
• Рекурсия
– Какво е рекурсия?
– Как работи рекурсията?
• Видове рекурсия
– Единична и множествена рекурсия
– Пряка и косвена рекурсия
– Опашна рекурсия
• Използване на рекурсия
–
–
–
–
Приложение
Основни принципи
Предимства и недостатъци
Оптимизация
3. Съдържание 2/2
• Примери за рекурсия
–
–
–
–
–
Факториел
Числа на Фибоначи
Обръщане на масив
Двоично търсене в сортиран масив
Търсене на път в лабиринт
4. Рекурсия
• Какво е „рекурсия“?
– За определение на „рекурсия“ вж. „Какво е рекурсия“?
– Рекурсия в най-общ смисъл е представяне на един обект или
процес чрез самия него
– Рекурсия в математиката е изразяване на една функция чрез
самата нея
– Рекурсия в компютърното програмиране е решение, при което
едно функция използва сама себе си
5. Рекурсия
• Как работи извикването на функция?
– При извикване на функция, в стека се запазва информация за
локалните променливи и мястото на извикването и параметрите
– При връщане от извикване на функция, изпълнението продължава
от мястото на извикването, като локалните променливи и
параметрите се възстановяват
• Как работи рекурсията?
– Описаната схема на извикване на функции работи при
произволен брой вложени извиквания на различни функции
– В частност тези извиквания могат да бъдат рекурсивни
– Внимание: При прекалено много вложени извиквания, стекът може
да се препълни това да доведе до грешка и прекратяване
изпълнението на програмата
6. Рекурсия
1. Директор към секретарката:
– „Заминаваме на командировка в чужбина за седмица.“
2. Секретарката към мъжа си:
– „Заминавам с шефа на командировка в чужбина за седмица.“
3. Мъжът към любовницата си:
– „Жена ми заминава в командировка. Тази седмица сме заедно.“
4. Любовницата (учителка) към ученика си:
– „Болна съм, тази седмица. Няма да имаме уроци.“
5. Ученикът към дядо си:
– „Дядо, тази седмица идвам нагости, няма да имам уроци.“
6. Дядото (директорът) към секретарката си:
– „Внукът идва нагости тази седмица. Командировката се отлага.“
7. Go to 1.
7. Видове рекурсия
• Единична и множествена рекурсия
– Единична рекурсия е тази, при която има само едно рекурсивно
извикване
– Множествената рекурсия е тази, при която има повече от едно
рекурсивно извикване
• Пряка и косвена рекурсия
– Пряка рекурсия е тази, при която функцията извиква себе си
директно
– Косвена (непряка) рекурсия е тази, при която функцията извиква
себе си чрез извикване на друга функция
– Косвената рекурсия се нарича още споделена рекурсия, тъй като
всички функции участващи в нея се извикват рекурсивно, макар и
косвено
8. Видове рекурсия
• Опашна рекурсия
– Опашно извикване е извикване на функция като последно действие
в друга функция
– Извикваната функция може да връща резултат, който да се върне
на извикващата функция
– Ако в резултат на опашното извикване в дадена функция същата
функция бъде извикана отново, то говорим за опашна рекурсия
– С други думи, опашна рекурсия е тази, при която рекурсивното
извикване се осъществява в „опашката“ на функцията
– Опашната рекурсия винаги може да бъде сведена до итерация
(оптимизация на опашна рекурсия)
9. Използване на рекурсия
• Приложение
– Решаване на математически задачи, решенията на които са
зададени с рекурсивна връзка
– Разбиване на решението на по-малки сходни решения и
обединяване на техните резултати („разделяй и владей“) –
динамично програмиране/оптимиране.
– Рекурсия + таблици за търсене = мемоизация
• Основни принципи
– Всяко рекурсивно извикване трябва да опростява решението
– Рекурсията трябва да съдържа условие за край (т.нар. дъно на
рекурсията)
10. Използване на рекурсия
• Предимства
– Опростява решението на множество задачите чрез разбиването
им на малки прости подзадачи
• Недостатъци
– Значително по-бавна работа, поради засилено използване на
стека
– Прекомерно използване на стек
• Оптимизация
– Свеждане на рекурсията до итерация, когато това е възможно
• Използване на собствени стекови структури за симулиране на рекурсия
– Използване на мемоизация, когато рекурсивните извиквания дават
еднозначен резултат при едни и същи аргументи
11. Примери за рекурсия – факториел
• Какво е факториел?
– Класическа дефиниция
n! = 1. 2 . 3 . … . (n-1) . n , n ∈ ℕ
0! = 1 (граничен случай)
– Рекурсивна дефиниция
n! = n . (n-1)! , n ∈ ℕ
0! = 1 (дъно на рекурсията)
• Факториелът е бързо растяща функция
• За големи стойности, използвайте
BigInteger вместо int
12. Примери за рекурсия – факториел
• Итеративно намиране на
факториел
– Итеративна дефиниция:
n! = 1. 2 . 3 . … . (n-1) . n , n ∈ ℕ
0! = 1 (граничен случай)
// факториел - итеративно
int GetFactorialIter(int n)
{
int result = 1;
// n! = 1.2.3. … .n
for (int i = 1; i <= n; i++)
result *= i;
return result;
}
13. Примери за рекурсия – факториел
• Рекурсивно намиране на
факториел
– Рекурсивна дефиниция:
n! = n . (n-1)! , n ∈ ℕ
0! = 1 (дъно на рекурсията)
// факториел – рекурсивно
int GetFactorialRec(int n)
{
// 0! = 1
if (n <= 0)
return 1;
// n! = n . (n-1)!
return n * GetFactorialRec(n-1);
}
14. Примери за рекурсия – числа на Фибоначи
• Кои са числата на Фибоначи?
– Рекурсивна дефиниция:
Fn = Fn-1 + Fn-2
F0 = 1, F1 = 1
// числа на Фибоначи – рекурсивно
int GetFibonacciNumbersRec(int n)
{
// F0 = 1, F1 = 1
if (n <= 1) return 1;
// Fn = Fn-1 + Fn-2
return GetFibonacciNumbersRec(n - 1)
+ GetFibonacciNumbersRec(n - 2);
}
15. Примери за рекурсия – обръщане на масив
• Рекурсивно обръщане на
масив
– Ако краищата на масива са се
разминали, се връщаме
обратно
– Разменяме крайните елементи
на масива
– Обръщаме подмасива от
елемент 1 до елемент n-2
// обръщане на масив – рекурсивно
void ReverseArrayRec(int[] a,
int start, int end)
{
// краищата на масива са се разминали
if (end <= start)
return;
// разменяме крайните елементи
int t = a[end];
a[end] = a[start];
a[start] = t;
// обръщаме подмасива
ReverseArrayRec(a, start + 1, end – 1);
}
16. Примери за рекурсия – дв. търсене в сорт. масив
• Двоично търсене в сортиран масив
– Ако краищата на масива са се
разминали, се връщаме неуспешно
– Взимаме средния елемент и
стойността му наричаме „ключ“
– Ако ключът:
• е по-малък от елемента, който търсим,
търсим в дясно стоящия подмасив
• е по-голям от елемента, който търсим,
търсим в ляво стоящия подмасив
• съвпада с елемента, който търсим,
връщаме индекса му
17. Примери за рекурсия – търсене на път в лабиринт
• Търсене на път в лабиринт
– Рекурсивното търсене на път в лабиринт се основава на принципа
„проба-грешка“
– На всяка стъпка:
1. Проверяваме дали сме стигнали. Ако да, се връщаме успешно.
2. Маркираме настоящата позиция като настъпана
3. Стъпваме във всяка една посока, докато не се
изчерпат и/или не сме намерили пътя
18. Задачи за упражнение
• Създайте програма със следните функции:
– CreateMatrix, която създава масив от елементи от
тип ConsoleColor попълнен произволно с бяло
(White) и черно (Black).
– ShowMatrix, която визуализира създадения масив по
подходящ начин
– FillMatrix, която започва да запълва бял регион с червено
използвайки рекурсивния flood fill алгоритъм, започвайки от
произволна бяла позиция.
– Нека във всяко рекурсивно извикване на FillMatrix се извиква и
ShowMatrix, така че визуализираният масив да се опреснява.
19. Задачи за упражнение
• Направете рекурсивна функция, която да намира и
изпечатва наименованията на всички файлове и техните
големини намиращи се в папка с подадено име и на
подадена дълбочина в нейните подпапки.
– За да вземете списък с наименованията на папките в дадена
папка, използвайте
string[] folderNames = Directory.GetDirectories(path);
– За да вземете списък с наименованията на файловете в дадена
папка, използвайте:
string[] fileNames = Directory.GetFiles(path);
– За да вземете големината на файл, използвайте код като следния:
long length = new FileInfo(fileName).Length;