Download - Programowanie I
Programowanie I
Rekurencja
Wstęp - funkcje i dane rekurencyjne• Definicja: Mówimy, że funkcja lub typ danych są rekurencyjne,
jeżeli w ich definicji następuje odwołanie do nich samych. • Zwykle rekurencja powstaje z powodu definiowania danej wielkości
przez samą siebie, np. znana funkcja silnia może być zdefiniowana matematycznie następująco (najpierw wykonujemy operacje w najbardziej zagnieżdżonych nawiasach):
• n*(n-1)*...*1=n*((n-1)*...*1)• Jej odpowiednia definicja programowa jest następująca:• silnia(n)= 1, gdy n=0• n*silnia(n-1) dla n>0• Natomiast rekurencyjny typ danych możemy określić następująco:
rozważmy zbiór B+ wszystkich niepustych ciągów złożonych z elementów ustalonego alfabetu B. B+ może być określona następująco:– jeśli b B, to <b> B+,– jeśli w B+ i b B, to w<b> B+.
Wstęp• Funkcje rekurencyjne pozwalają wyrazić nam w
sposób jawny szczególny rodzaj złożenia czynności – tzw. złożenie rekurencyjne.
• Z takim rodzajem złożenia spotkaliśmy się już w semantyce instrukcji “while” i “do”.
• Dla instrukcji „while” możemy złożenie rekurencyjne zdefiniować następująco:void dopóki(int B)
{if (B) { S; dopóki(B)}
}
Wstęp• a dla „do”
void powtarzaj(int B)
{S;
if (B)
{powtarzaj(B);
}
}
• Podobnie możemy postąpić dla instrukcji for.
Wstęp• Algorytmy zapisane za pomocą rekurencji są zapisywane
bardzo prosto i zapisanie ich za pomocą czynności określanych iteracyjnie nie musi być łatwe.
• Przykłady zapisu znanych instrukcji za pomocą ich odpowiedników rekurencyjnych pokazują, że iterację można zapisać za pomocą rekurencji, natomiast odwrotna zasada nie jest prawdziwa.
• Przykładem takiej sytuacji mogą być algorytmy, w których realizacja wymaga rozwiązania za pomocą odpowiedniej, zmiennej w zależności od danych, ilości pętli iteracyjnych.
• Należy jednak dodać, że pochopne stosowanie rekurencji może powodować poważne kłopoty z pamięcią i należy szczególnie ostrożnie rozważać problem skończoności obliczeń.
Wstęp - rola stopu• Np. procedura silnia, która może być zdefiniowana
następująco:
int silnia(int n)
{
if (n=0)
silnia=1;
else
silnia=n*silnia(n-1)
}
• Co dla n<0?. Czy można to poprawić?
Wstęp - działanie rekurencji
• Przykład:void p1(int n){if (n>0) p1(n-1);cout <<n;}
int main(){ p1(4); getch(); return 0;}
Wywołujemy p1(4), jaki wynik i dlaczego?
Wstęp - działanie rekurencji
• Przykładvoid p2(int n){cout <<n;if (n>0) p2(n-1);}
int main(){ p2(4); getch(); return 0;}
Wywołujemy p2(4), jaki wynik?
Przykłady rekurencji - wyjściowe wzory
0n,1
0n,)!1n(*n!n
1n
1i
n
1i
xixnxi
0!x,xx
0x,0x
)x,...,xmin(x),x,...,xmin(
)x,...,xmin(x,x1n,x
)x,...,xmin(
1n1n1n1
1n1nn
1
n1
Wstęp - przykłady• Sumujemy do napotkania 0
int suma()
{
int x;
cin>>x;
if (x!=0) return suma()+x;
else return 0;
}
int main(int argc, char **argv)
{
cout<<suma();
getch();
return 0;
}
Przykłady - silniaint silnia(int n)
{
if (n>0) return n*silnia(n-1);
else return 1;
}
int main(int argc, char **argv)
{
cout<<silnia(4);
getch();
return 0;
}
Rekurencja - przykład
Wzór: fibn=fibn-1+fibn-2, fib1=fib2=1Implementacja
int fib (int n) {
if (n < 3 ) return (1);
else {
return( fib(n-2) + fib(n-1)); }
Rekurencja• Przykład algorytmu, którego nie można
zaimplementować za pomocą iteracji:• Problem n-hetmanów:
– ustaw n-hetmanów na szachownicy o wymiarze nxn, tak by się wzajemnie nie szachowały.
• Idea algorytmu (algorytm z nawrotami):– stawiamy hetmana na wybranym polu, – następne hetmany dostawiamy tak, by się nie
szchowały, – jeśli to niemożliwe, to zdejmujemy ostatniego
hetmana i powtarzamy dla innych pól. – Przykład działania na tablicy.
Rekurencja• Bardziej formalne rozwiązanie:
• Stopniowa konstrukcja polega na tym, że znajdujemy reprezentację rozwiązania x postaci [x1,x2,...,xn] konstruując kolejno [x1], [x1,x2], [x1,x2,x3] itd. w ten sposób, że – 1.każde przejście od [x1,...,xj] do [x1,...,xj,xj+1] jest prostsze niż obliczanie
całego próbnego rozwiązania,
– 2.jeśli q jest predykatem charakteryzującym rozwiązanie, to musi zachodzić: j ((1<=j<=n)=>(q(x)=>q([x1,...,xj])
• Warunek 2 oznacza, że aby otrzymać pełne i poprawne rozwiązanie musimy uzupełnić rozwiązanie częściowe tak, by spełniało ono kryterium poprawności. Jeżeli takie uzupełnienie nie jest w danym momencie możliwe, to odwołujemy pewne poprzednie uzupełnienia tego rozwiązania, czyli skracamy je do [x1,...,xi], gdzie i<j i próbujemy inne uzupełnienie. Takie powroty i próbowanie nowego rozwiązania nazywamy nawracaniem.
• Implementacja samodzielnie.