Rekurze
volání podprogramu opětovně v jeho těle –v době, kdy předchozí volání ještě nebylo ukončeno Druhy rekurze přímá rekurze nepřímá rekurze
Přímá rekurze podprogram volá sám sebe void A(…) { A(); }
Nepřímá rekurze aktivují se vzájemně dva podprogramy void A(…) { B(); } void B(…) { A(); }
pravidla tvorby rekurzivní funkce –musí být definována podmínka pro ukončení rekurze v algoritmu se musí ověřit, zda nenastala koncová situace –v každém kroku musí dojít ke zjednodušení problému
Příklad 1 Napište rekurzivní funkci pro výpočet faktoriálu long fakt(int n) { if (n<=1) return 1; else return n*fakt(n-1); }
Jak se rekurze volá? fakt(2) int main() { fakt(2); } return 2*fakt(1) return 1 1 2
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n V registru procesoru AX se předává návratová hodnota 2584 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x V registru procesoru AX se předává návratová hodnota 2584 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x V registru procesoru AX se předává návratová hodnota 2584 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n V registru procesoru AX se předává návratová hodnota 2584 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n V registru procesoru AX se předává návratová hodnota 2584 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n x V registru procesoru AX se předává návratová hodnota 2584 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n x V registru procesoru AX se předává návratová hodnota 2584 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n x 1 n V registru procesoru AX se předává návratová hodnota 2584 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n x 1 n x V registru procesoru AX se předává návratová hodnota 2584 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n x 1 n x V registru procesoru AX se předává návratová hodnota 2584 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n x 1 n 5 V registru procesoru AX se předává návratová hodnota 1 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n x 1 n 1 V registru procesoru AX se předává návratová hodnota 1 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n x V registru procesoru AX se předává návratová hodnota 1 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n x V registru procesoru AX se předává návratová hodnota 2584 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n V registru procesoru AX se předává návratová hodnota 2 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n x n V registru procesoru AX se předává návratová hodnota 2 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník 11 2 n x V registru procesoru AX se předává návratová hodnota 2 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník 11 6 n x V registru procesoru AX se předává návratová hodnota 2 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník 11 n V registru procesoru AX se předává návratová hodnota 6 AX: 4589 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } 3 Zásobník n V registru procesoru AX se předává návratová hodnota 6 AX: 6 a
long fakt(int n) { long x; 1: if (n<=1) 2: return 1; 3: else { 4: x=fakt(n-1); 5: x = n*x; 6: return x; } int main() {long a; 10: a = fakt(3); 11: cout << a; } Zásobník V registru procesoru AX se předává návratová hodnota 6 AX: 6 a
Úloha 1 Napište rekurzivní funkci pro výpočet Fibbonaciho posloupnosti F(0) = 0 F(1) = 1 F(n) = F(n-1) + F(n-2)
rekurze, je-li příliš „hluboká“, způsobí růst velikosti zásobníku –v některých případech je možné ji nahradit pouhým cyklem –jindy se musí simulovat zásobník long fakt(int n) { long faktorial = 1; for(i=2;i<=n;i=i+1) faktorial = faktorial*i; return faktorial; }
Úloha 2 Je dána celá částka v Kč. Máme k dispozici mince v hodnotách 20 Kč, 10 Kč, 5 Kč, 2Kč, 1 Kč. Napište rekurzivní proceduru, která vytiskne na obrazovku složení částky z co nejmenšího počtu mincí (vytiskne seznam mincí). Domácí úloha: Odstraňte rekurzi (přepište proceduru pomocí cyklu).
Hanojské věže máme 3 tyče (1, 2, 3) na první tyči je věž z n disků naskládaných na sebe, spodní disk má největší průměr úkol: –přemístit věž z tyče 1 na tyč 2 pomocí třetí tyče 3 přesouváním disků za podmínek: v jediném kroku lze přenést jeden disk z tyče na tyč disk lze odložit pouze na tyč nelze položit disk o větším průměru na disk s menším průměrem.
triviální úloha –přenesení věže o výšce 1, tj. přenesení disku, z tyče na tyč –je reprezentována procedurou void Prenes_disk(int odkud, int kam), která v našem programu vypíše informaci o přesunu disku, např.: 1 -> 2 úvaha –chci-li přesunout věž např. o výšce 3 disky z tyče 1 na tyč 2 pomocí tyče 3, musím nejprve přesunout věž o výšce 2 (počítáno od shora) na tyč 3 pomocí 2, pak přesunout spodní největší disk z tyče 1 na tyč 2 a nakonec přesunout věž o výšce 2 z tyče 3 na tyč 2 pomocí tyče 1
odkud = tyč 1 kam = tyč 2 pomocí = tyč 3
void prenes_vez(int vyska, int odkud, int kam, int pomoci) { if (vyska == 1) prenes_disk(odkud,kam); else { prenes_vez(vyska-1, odkud, pomoci, kam); prenes_disk(odkud,kam); prenes_vez(vyska-1,pomoci,kam,odkud); }
Jaká je složitost algoritmu? přesunutí věže o n discích se skládá z přesunutí věže o (n-1) discích, přesunutí disku a přesunutí věže zpět o (n-1) discích počet kroků algoritmu (počet přesunutí disků) je dán rekurentním vztahem: F(n) = F(n-1) F(n-1) = 2F(n-1)+1, přičemž F(1) = 1
řešením rovnice je vztah F(n) = 2 n - 1 tj. počet přesunů disku u věže výšky n je roven 2 n - 1 složitost algoritmu je tedy O(2 n ) exponenciální
Aplikace rekurze Prohledávání s návratem (Backtracking) používá se pro hledání všech řešení daného problému princip: –řešení hledám po krocích, pokud je řešení nalezeno nebo nelze úlohu dále řešit, vrátím se o krok zpět úlohy: –hledání všech cest v bludišti –problém rozmístění 8 dam na šachovnici
Problém 8 dam na šachovnici úkol: –umístit 8 dam na šachovnici 8x8 polí, aby se vzájemně neohrožovaly princip: –umístím dámu na první řádek na první sloupec, další dámu na druhý řádek na třetí sloupec atd., pokud nemohu další dámu umístit, provedu návrat na předchozí řádek a dámu posunu
Problém 8 dam demonstrace na 4 dámách na šachovnici 4x4
Problém 8 dam
další dámu nemohu umístit, provedu návrat
Problém 8 dam
další dámu nemohu umístit, provedu návrat
Problém 8 dam
Rozděl a panuj (Divide and Conquer) používá se pro zjednodušení řešení složitého problému princip: – rozdělím prostor řešení na dva menší (pokud možno stejně velké) podprostory –vyřeším problém na každém podprostoru samostatně (stejnou technikou) –spojím obě řešení úlohy: –Hanojské věže –řazení: QuickSort
Hledání cest v bludišti
algoritmus s návratem (rekurzivní) –je-li to možné, v každé místnosti zkusím „jít na všechny“ světové strany do další místnosti –pokud jsem našel východ, vypíši cestu a vrátím se o krok zpět –vracím se o krok zpět, nemohu-li postoupit do další mísnosti (jsem v slepé uličče)
Jak bude principiálně algoritmus vypadat? Napišme jej v pseudokódu. Připomeňme si pravidla tvorby rekurzivní procedury: a)podmínka ukončení rekurze nalezení východu nebo slepá ulička b)zjednodušení problému v dalším kroku postoupil jsem do další místnosti (nalezená cesta je o 1 krok delší)
jdi_do_mistnosti(kam) { if (jsem_venku) { tiskni_cestu(); return; } else { if (mohu_na_sever) jdi_do_mistnosti(sever); if (mohu_na_jih) jdi_do_mistnosti(jih); if (mohu_na_vychod) jdi_do_mistnosti(vychod); if (mohu_na_zapad) jdi_do_mistnosti(zapad); } }
Donald Knuth Data + Algorithms = Programs Rozmyslíme si datové struktury a algoritmy nad nimi !
reprezentace bludiště –informace o velikosti – šířce a délce –dynamické dvourozměrné pole informací o místnostech typedef struct { int sirka, delka; TMistnost **plocha; } TBludiste;
reprezentace místnosti –informace o otevřenosti dveří na čtyři světové strany typedef enum { ZAVRENO=0,OTEVRENO=1} TStavDveri; typedef struct { TStavDveri sever; TStavDveri jih; atd; } TMistnost;
kam budu ukládat místnosti z nalezené cesty –nevím předem, jak bude cesta dlouhá –mám algoritmus s návratem, tj. při postupu do jiné místnosti potřebuji přidávat na konec cesty novou mísnost, při návratu ji z konce odebrat –nejlepší bude obousměrný spojový seznam prevence proti cyklu –booleovké pole místností, které jsem navštívil –mohu doplnit o přímý test, abych nešel na tu světovou stranu v místnosti, ze které jsem přišel
hlavička rekurzivní procedury void jdi_do_mistnosti(TBLudiste &bludiste, TSvetStrany kam_jdu, int x, int y, TCesta &cesta);
Úloha Vstupem programu jsou čísla N a K. Nalezněte všechny K-tice celých kladných čísel x 1,x 2,x 3, … x (K-1), x K, pro které platí x 1 <= x 2 <= x 3 <=…<= x (K-1) <=x K a x 1 + x 2 + x 3 +…..+ x (K-1) + x K = N.
Úloha Kostka domina je tvořena dvěma poli. Každé z nich může být prázdné nebo může obsahovat jistý počet teček od 1 do 6. Kostky se skládají do řady tak, že vedle sebe stojící kostky musí sousedit stejnými poli; za kostkou s prázdným polem nelze dát další kostku. Vstupem vašeho programu bude soubor se seznamem kostek domina, které máte k dispozici (na každém řádku bude dvojice čísel oddělená mezerou, představující počet teček). V seznamu se může opakovat i více stejných kostek. Zjistěte jakou nejdelší řadu lze z těchto kostek sestavit. Vytiskněte tuto řadu a její délku měřenou počtem kostek. Stačí jedno řešení.