rekurencja
Transkrypt
rekurencja
rekurencja 1 Rekurencja/rekursja • Alternatywny dla pętli sposób powtarzania pewnych czynności; kolejny etap – podzadanie poprzedniego • Rekursja może być zamieniona na iteracje • Cechy rekurencji – Rozłożenie problemu na problemy elementarne i na problem mniej skomplikowany niż wejściowy – Jasno określony warunek zakończenia (przypadek podstawowy, zdegenerowany) • Iluzja istnienia wielu kopii tego samego algorytmu (aktywacji) • Tylko jedna aktywacja jest aktywna w danej chwili 2 silnia silnia(4) →4*silnia(3) function silnia(x) if (x=0) then (silnia:=1; return) else ( silnia:=x*silnia(x-1)) silnia(3)→3*silnia(2) procedure silnia(x) wynik:=1; i:=1; repeat (wynik:=wynik *i; i:=i+1) until (i>x) silnia(2)→2*silnia(1) silnia(1)→1*silnia(0) silnia(0)=1 procedure silnia(x) wynik:=1; i:=1; while (i<=x) do (wynik:=wynik *i; i:=i+1) 3 Wyszukiwanie binarne li i=1,..n procedure szukaj(l,p,k,wartosc) if((k-p)<0) then (return – porażka) else j:=(p+k)/2; if (wartość=lj) then (return –sukces - j ) else if (wartość >lj) then szukaj(l,j+1,k,wartosc) else szukaj(l,p,j-1, wartosc) 4 Krzywe Hilberta H1- 4 puste krzywe H0 połączone liniami prostymi H2- 4 kopie H1 zmniejszone i obrócone połączone liniami prostymi 4 warianty: A: D ←A ↓ A → B A D B: C ↑ B → B ↓ A C: B → C ↑ C ← D D: A ↓ D ← D ↑ C A B 5 Krzywe Sierpińskiego A D B S2 C S1 S3 A: A BD A B: B C ↓↓ A B C: C D <= B C D: D A ↑↑ C S1 S2 S3 S4 S: A B C D D 6 Wieże Hanoi X Y Z X Y Z 3,X,Y,Z 2,X,Z,Y 1,X,Y,Z procedure move(n,X,Y,Z) if (n=1) then (X→Y) else (move(n-1,X,Z,Y); X→Y; move(n-1,Z,Y,X)) X→Z 1,Y,Z,X X→Y Y→Z X→Y 2,Z,Y,X 1,Z,X,Y Z→Y 1,X,Y,Z Z→X X→Y 7 Wieże Hanoi X Y Z X procedure move(n,X,Y,Z) repeat (przenies najmniejszy krazek na nastepny kolek; przenies nienajmniejszy krazek (jedyny ruch)) until(OK) Y Z X→Y X→Z Y→Z X→Y Z→X Z→Y X→Y 8 Wieże Hanoi – Liczba pojedynczych przeniesień krążka= 2N-1 N – liczba krążków – Złożoność wykładnicza – Problem Tybetańczyków – 64 krążki • Przeniesienie 1 krążka – 10 s • 5 bilionów (1012) lat 9 Algorytmy z powrotami • Droga skoczka szachowego • Problem ośmiu hetmanów (Gauss 1850; 12 różnych istotnie rozwiązań) • Problem trwałego małżeństwa 10 Droga skoczka szachowego procedure proba repeat (wybierz ruch; if (dopuszczalny) then (zapisz go;) if (sa puste pola) then (proba; if(nieudany) then (wykresl ostatni zapis) else udany)) until (ruch udany lub brak ruchu) 11 Niebezpieczeństwa rekurencji • Powtarzanie części obliczeń (ciąg Fibonacciego) – programowanie dynamiczne • Przepełnienie stosu • Błąd – nieskończona liczba wywołań • Różne kompilatory –różne wyniki identycznych programów 12 Ciąg Fibonacciego fib(0)=0 fib(1)=1 fib(n)=fib(n-1) + fib(n-2) dla n>=2 fib(0) fib(2) function fib(x) if(x=0) then (fib:=0) else if(x=1) then (fib:=1) else fib:=fib(x-1)+fib(x-2) fib(1) fib(4) fib(1) fib(3) fib(0) fib(2) fib(1) 13 Przepełnienie stosu procedure MacCarthy(x) if (x>100) then (wynik:=x-10) else (wynik:=MacCarthy(MacCarthy(x+11))) MC(96)---MC(MC(107))----MC(97)----MC(MC(108))----MC(98)----MC(MC(109))----MC(99)----MC(MC(110))----MC(100)--MC(MC(111))----MC(101)----91 14 nieskończona liczba wywołań • Upraszczanie nie doprowadza do przypadku elementarnego function fun(n) if(n=1) then return 1 else (if (n%2 =0) then else ) (fun:=fun(n-2)*n; return fun) (fun:=fun(n-1)*n; return fun) 15 Nieskończony ciąg wywołań function fun(n,m) if (n=0) then return 1 else return fun(n-1,fun(n-m,m) fun(1,0) → fun(0,fun(1,0)) → fun(0,fun(1,0)) → fun(0,fun(1,0)) …. Parametry funkcji rekurencyjnej wartościowane jako pierwsze 16 Przykłady funkcji rekurencyjnych w języku C 17 silnia double silnia(int x) { if(x<=0) return 1; else return (x*silnia(x-1)); } 18 Zamiana na liczbę binarną void zamiana(int liczba) { if(liczba>1) {zamiana(liczba/2); printf("%d",liczba%2); } else printf("%d\n",liczba); } 19 Binarne wyszukiwanie int szukaj(int p, int k, int *tab, int x) { int s; if(k==p) if(tab[p]==x) return p; else return -1; else { s=(p+k)/2; if(tab[s]==x) return s; else if(x>tab[s]) return szukaj(s+1,k,tab,x); else return szukaj(p,s,tab,x); } } 20 Odwracanie posortowanej tablicy void odw(tab,p,k) int p,k; int *tab; { int pom; if(p<k) { pom=tab[p]; tab[p]=tab[k]; tab[k]=pom; odw(tab,p+1,k-1); } } 21 Zliczanie ostatnich 0 w reprezentacji binarnej liczby całkowitej int licz0(int k) { if(k==0) return 1; if (k%2) return 0; else return 1+licz0(k/2); } 22 Zliczanie ostatnich 1 w reprezentacji binarnej liczby całkowitej int licz1(int k) { if (k%2) return 1+licz1(k/2); else return 0; } 23 Algorytm Euklidesa int nwd_euklides(int m, int n) { if(n==0 )return m; return nwd_euklides(n, m%n); } 1. 2. 3. Przypisz zmiennym M i N odpowiednio większą i mniejszą wartość wejściową Podziel M przez N, resztę z dzielenia nazwij R Jeśli R nie jest zerem, to przypisz zmiennej M wartość N, zmiennej N wartość R i powróć do kroku 2, w przeciwnym razie NWD to wartość N 24 Szukanie wartości minimalnej w tablicy int mint( n, t, i) int n, t[n],i; { int kk; if(i==n-1) return t[i]; else { kk = mint(n,t,i+1); if ( t[i] < kk ) return t[i]; else return kk; } } 25 Wyszukiwanie binarne – maksymalny element tablicy int max(a,p,k) int *a,p,k; { int s,max1,max2; if(p==k) return a[p]; s=(p+k)/2; max1=max(a,p,s); max2=max(a,s+1,k); if (max1>max2) return max1; else return max2; } 26 Wyszukiwanie binarne strategia ”dziel i rządź” • Dwa wywołania rekurencyjne • Każde obsługuje połowę zbioru danych wejściowych 27 Metoda „dziel i zwyciężaj” – Podział problemu na kilka mniejszych, podobnych do początkowego – Rozwiązywanie rekurencyjne podproblemów – Łączenie rozwiązań • Etapy każdego poziomu rekursji – Dziel – Zwyciężaj – rozwiązanie małych podproblemów rekurencyjnie, dla małych rozmiarów – metody bezpośrednie – Połącz • Przykład – sortowanie przez scalanie 28 Programowanie dynamiczne • • • Poprawia metodę „dziel i zwyciężaj” w sytuacji wielokrotnego rozwiązywania tych samych problemów – Czas wykładniczy liniowy Wstępujące – Obliczenie wartości począwszy od najmniejszych wartości; – Obliczenie bieżącej wartości korzystając z poprzednio obliczonych wartości – Warunek – istnieje możliwość gromadzenia obliczonych wartości Zstępujące (zapamiętywanie) – Procedura rekurencyjna sprawdza zapisane wartości i zapisuje obliczoną przez siebie wartość 29 Liczby Fibonacciego int F(int i) { if(i<1) return 0; if(i==1) return 1; return F(i-1)+F(i-2); } int main() { int k; cin>>k; cout<<"F("<<k<<")="<<F(k); } const int maxN=100; int F(int i) { static int Fib[maxN]; if(i<0) return 0; if(Fib[i]!=0) return Fib[i]; int t=i; if(i>1) t=F(i-1)+F(i-2); return Fib[i]=t; } int main() { int k; cin>>k; cout<<"F("<<k<<")="<<F(k); } 30 Liczby Fibonacciego • Program1 – czas wykładniczy T (n) O( n ), 1.618 a , złoty podziałodcinka b Np. – obliczenie F(n) – 1 s obliczenie F(n+9) – 1min obliczenie F(n+18) – 1h ab a a b 9 60 • Program2 - wykorzystuje tablicę statyczną (na początku wypełniona 0) T(n)=O(n) F(45)=1 836 311 903 - największa 32-bitowa całkowita liczba Fibonacciego -potrzebna tablica 46-elementowa 31 Liczby Fibonacciego – F6 6 5 4 4 3 3 3 2 2 2 2 1 1 1 1 6 2 1 1 1 1 1 1 1 1 1 3 3 1 1 1 2 1 3 2 4 3 5 5 6 8 7 15 wypełnienie tablicy 2 2 0 1 5 4 1 0 8 5 Program1 25 wywołań 0 Program2 6 wywołań 32 Symbol Newtonarekurencja n n 1 n 1 dla m 0, m n m m m 1 n 1 dla m 0 m n m T (n) O(a n ) int symbol_newtona(int n, int m) { int sn; if(m==0 ||m==n) return 1; sn=symbol_newtona(n-1,m)+symbol_newtona(n-1,m-1); return sn; } sn(4,2)= sn(3,2) + sn(3,1) = sn(2,2)+sn(2,1)+sn(2,1)+sn(2,0)= 1+sn(1,1)+sn(1,0)+sn(1,1)+sn(1,0) +1=6 33 Symbol Newtona programowanie dynamiczne int sn(int n, int m) { static int tab[maxN]; int i,j; if(m==0 ||m==n) return 1; for(j=0;j<=n;j++) tab[j]=1; for(i=2;i<=n;i++) for(j=i-1;j>0;j--)//j>m-1 tab[j]=tab[j-1]+tab[j]; return tab[m]; } i j w i-tym kroku tab[j] = T (n) O(n 2 ) 34 Symbol Newtona programowanie dynamiczne i w i-tym kroku tab[j] = j i=1 tab[0] 1 tab[1] 1 tab[2] 1 tab[3] 1 tab[4] 1 tab[5] 1 m n=6 i=2 i=3 i=4 i=5 i=6 1 1 2 3 4 5 6 3 6 10 15 4 10 20 5 15 n 1 1 1 1 1 2 3 4 5 1 3 6 10 1 4 10 1 5 1 6 4 dla i=4 tab[3] = = 4 3 6 dla i=6 tab[3] = = 20 3 n m 5 2 n>m n = 0, 1,… m = 0, 1,…. 4 335