Podstawy programowania (język C) funkcje rekurencyjne Wykład 12. Tomasz Marks - Wydział MiNI PW -1- Tomasz Marks - Wydział MiNI PW -2- Funkcje rekurencyjne (1) W języku C funkcja moŝe wywoływać samą siebie. Taką funkcję nazywamy funkcją rekurencyjną. Rekurencja moŝe być realizowana bezpośrednio, jak i pośrednio. N.p. Schemat funkcji int F ( int x ) int y, z; z = F( y ); ilustruje realizację rekurencji bezpośredniej. Tomasz Marks - Wydział MiNI PW -3- Funkcje rekurencyjne (2) Ilustracja rekurencji pośredniej jest trochę bardziej złoŝona. N.p. int H ( int x ); // niezbędna deklaracja int G ( int x ) // funkcja rekurencyjna int y, z; z = H( y ); int H ( int x ) // funkcja pośrednicząca int y, z; z = G( y ); W powyŝszym przykładzie rekurencyjna funkcja G wywołuje samą siebie za pośrednictwem funkcji H. Oczywiście, głębokość takiego pośrednictwa moŝe być większa. Tomasz Marks - Wydział MiNI PW -4-
Funkcje rekurencyjne (3) Klasycznym przykładem jest uŝycie rekurencji do obliczania wartości silni (! ): 0! == 1 1! == 1 dla n >= 2 n! == 1* 2 *...* (n-1) * n Ten ostatni wzór moŝe być zrealizowany iteracyjnie int i, s=1; for ( i=2; i<=n; ++i ) s = s * i; co pozwala napisać definicję funkcji: int silnia ( int n ) int i, s=1; if ( n <= 1 ) return 1; for ( i=2; i<=n; ++i ) s *= i; return s; Ale wzór dla n >= 2 Funkcje rekurencyjne (4a) n! == 1* 2 *...* (n-1) * n moŝemy zapisać w postaci: albo n! == n * (n-1)! silnia(n) == n * silnia(n-1) co prowokuje, Ŝeby napisać definicję funkcji: int Silnia ( int n ) if ( n < 2 ) return 1; return n * Silnia( n-1 ); Tomasz Marks - Wydział MiNI PW -5- Tomasz Marks - Wydział MiNI PW -6- Funkcje rekurencyjne (4b) Funkcje rekurencyjne (5) Ale wzór dla n >= 2 n! == 1* 2 *...* (n-1) * n moŝemy zapisać w postaci: albo n! == n * (n-1)! silnia(n) == n * silnia(n-1) ewentualnie: int Silnia ( int n ) return ( n < 2 )? 1 : n * Silnia( n-1 ); Tomasz Marks - Wydział MiNI PW -7- Tomasz Marks - Wydział MiNI PW -8-
Funkcje rekurencyjne (5) Funkcje rekurencyjne (6) Inny przykład funkcji rekurencyjnej moŝna znaleźć w ksiąŝce Kerninhana i Ritchie'go. Funkcja realizuje wypisywanie liczby całkowitej w postaci ciągu znaków. #include <stdio.h> /* printd: wypisz n dziesiętnie */ void printd ( int n ) if ( n < 0 ) putchar ( '-' ); n = -n; if ( n / 10 ) printd ( n / 10 ); putchar ( n % 10 + '0' ); Tomasz Marks - Wydział MiNI PW -9- Tomasz Marks - Wydział MiNI PW -10- Sortowanie (1) Sortowanie jest zadaniem polegającym na porządkowaniu elementów jakiegoś zbioru według ustalonego kryterium. SORTOWANIE N.p. porządkowanie tablicy liczb według rosnących/malejących wartości jej elementów porządkowanie listy struktur zawierających dane osobowe według alfabetycznej kolejności nazwisk porządkowanie pliku dyskowego zawierającego opisy magazynowanych towarów według rosnących/malejących wielkości cen Istnieje bardzo wiele algorytmów sortowania. N.p. bąbelkowe, przez wstawianie, quicksort,... Wybór zaleŝy od sposobu reprezentacji i wielkości (liczebności elementów) rozpatrywanego zbioru. Tomasz Marks - Wydział MiNI PW -11- Tomasz Marks - Wydział MiNI PW -12-
Sortowanie (2) Sortowanie (3a) Jednym z najprostrzych algorytmów sortowania jest tzw. sortowanie bąbelkowe. Metoda polega na wielokrotnym przeglądaniu zawartości zbioru i porównywaniu wartości kolejnych par jego elementów. JeŜeli para jest w niewłaściwej kolejności, to jej elementy są zamieniane miejscami. Dla przykładu rozpatrzmy tablicę int A[ 5 ] = 2, 5, 8, 1, 4 ; którą chcemy uporządkować według rosnących wartości jej elementów. To nasza tablica, którą chcemy uporządkować w kolejności rosnących wartości elementów. Tomasz Marks - Wydział MiNI PW -13- Tomasz Marks - Wydział MiNI PW -14- Sortowanie (3b) Sortowanie (3c) Pierwsza para elementów. We właściwym porządku. Kolejna para elementów. TeŜ we właściwym porządku. Tomasz Marks - Wydział MiNI PW -15- Tomasz Marks - Wydział MiNI PW -16-
Sortowanie (3d) Sortowanie (3e) Elementy w niewłaściwym porządku. Trzeba je zamienić miejscami. Po wykonaniu zamiany. Tomasz Marks - Wydział MiNI PW -17- Tomasz Marks - Wydział MiNI PW -18- Sortowanie (3f) Sortowanie (3g) Elementy w niewłaściwym porządku. Trzeba je zamienić miejscami. Po wykonaniu zamiany. Tomasz Marks - Wydział MiNI PW -19- Tomasz Marks - Wydział MiNI PW -20-
Sortowanie (3h) Sortowanie (4) MoŜemy łatwo opisać jeden przebieg algorytmu: int n=5, i; Po wykonaniu pierwszego przebiegu element o największej wartości znajduje się na ostatniej pozycji. for ( i = 0; i < n-1; i++ ) if ( A[ i ] > A[ i+1 ] ) int tmp = A[ i ]; A[ i ] = A[ i+1 ]; A[ i+1 ] = tmp; W kolejnym przebiegu warunek i < n-1 powinniśmy zastąpić przez i < n-2 w następnym przez i < n-3 itd.., itd.., aŝ do i<1 co moŝna zapisać jako i < n-(n-1) Tomasz Marks - Wydział MiNI PW -21- Tomasz Marks - Wydział MiNI PW -22- Czyli cały algorytm int n=5, i, j; for ( j = 1; j< n; j++ ) for ( i = 0; i < n-j; i++ ) if ( A[ i ] > A[ i+1 ] ) int tmp = A[ i ]; A[ i ] = A[ i+1 ]; A[ i+1 ] = tmp; Sortowanie (5) ZauwaŜmy, Ŝe jeśli dla jakiegoś j pętla for ( i=0; i<n-j; i++ )... "wykręci" się bez wykonania choćby jednej zamiany, to znaczy, Ŝe wszystkie elementy są juŝ we właściwej kolejności, a więc obliczenia naleŝy przerwać. Tomasz Marks - Wydział MiNI PW -23- Sortowanie (6) PowyŜszą uwagę uwzględnimy definicji funkcji: void bubblesort ( int A[ ], int n ) int i, j; if ( n < 2 ) return; for ( j = 1; j< n; j++ ) int przerwij = 1; for ( i = 0; i < n-j; i++ ) if ( A[ i ] > A[ i+1 ] ) int tmp = A[ i ]; A[ i ] = A[ i+1 ]; A[ i+1 ] = tmp; przerwij = 0; if ( przerwij ) break; Tomasz Marks - Wydział MiNI PW -24-
Sortowanie (7) A teraz zobaczmy rekurencyjną wersję naszej funkcji: void bubsort ( int A[ ], int n ) int i, przerwij = 1; if ( n < 2 ) return; // zaczynamy sortowanie tablicy n-elementowej for ( i = 0; i < n-1; i++ ) if ( A[ i ] > A[ i+1 ] ) int tmp = A[ i ]; A[ i ] = A[ i+1 ]; A[ i+1 ] = tmp; przerwij = 0; jeszcze na temat... SORTOWANIE funkcja qsort( ) if ( przerwij ) return; // tablica jest juŝ posortowana bubsort( A, n-1 ); // trzeba posortować tablicę (n-1)-elementową Tomasz Marks - Wydział MiNI PW -25- Tomasz Marks - Wydział MiNI PW -26- qsort (1) Funkcja standardowa qsort ma deklarację w pliku stdlib.h void qsort ( void *base, size_t n, size_t size, int (*cmp)(const void* a, const void* b) ); Porządkuje w kolejności rosnącej tablicę tab[0], tab[1],... tab[n-1] składającej się z o rozmiarze n elementów size bajtów, cmp jest wskazaniem funkcji porównującej 2 elementy. Parametr base przekazuje adres początku tablicy tab, t.zn. base == (void *) tab Tomasz Marks - Wydział MiNI PW -27- qsort (2) Funkcja wskazana przez cmp musi zwracać: wartość ujemną, gdy obiekt *a jest mniejszy obiektu *b; zero, gdy obiekt *a jest równy *b; wartość dodatnią, gdy obiekt *a jest większy od obiektu *b. N.p. dla tablicy danych typu int odpowiednia funkcja moŝe mieć postać: int i_cmp ( const void* a, const void* b ) int A = *(int*)a, B = *(int*)b; return A - B; lub int i_cmp ( const void* a, const void* b ) return *(int*)a - *(int*)b; Tomasz Marks - Wydział MiNI PW -28-
qsort (3) N.p. dla tablicy danych typu double odpowiednia funkcja moŝe mieć postać: int d_cmp ( const void* a, const void* b ) double A = *(double*)a, B = *(double*)b; return (A-B<0)? -1 : ( (A-B>0)? 1 : 0 ); N.p. dla tablicy danych typu struct Osoba char nazwisko [20]; char imie [20]; int wiek; double waga; qsort (4) dla uporządkowania według alfabetycznej kolejności nazwisk odpowiednia funkcja moŝe mieć postać: UWAGA: return A B; nie będzie tu właściwym rozwiązaniem. Dlaczego??? int On_cmp ( const void* a, const void* b ) return strcmp ( (struct Osoba*)a->nazwisko, (struct Osoba*)b->nazwisko ); Tomasz Marks - Wydział MiNI PW -29- Tomasz Marks - Wydział MiNI PW -30- N.p. dla tablicy danych typu struct Osoba char nazwisko [20]; char imie [20]; int wiek; double waga; qsort (5) dla uporządkowania według alfabetycznej kolejności nazwisk i imion odpowiednia funkcja moŝe mieć postać: int Oni_cmp ( const void* a, const void* b ) int k = strcmp ( (struct Osoba*)a->nazwisko, (struct Osoba*)b->nazwisko ); if ( k ) return k; return strcmp ( (struct Osoba*)a->imie, (struct Osoba*)b->imie ); qsort (6) Dla tablic danych int A[100]; double B[30]; struct Osoba C[75]; wywołania porządkujące powyŝsze tablice powinny mieć postać: qsort ( A, 100, sizeof(int), i_cmp ); qsort ( B, 30, sizeof(double), d_cmp ); qsort ( C, 75, sizeof(struct Osoba), On_cmp ); // dla nazwisk qsort ( C, 75, sizeof(struct Osoba), Oni_cmp ); // dla nazwisk i imion Tomasz Marks - Wydział MiNI PW -31- Tomasz Marks - Wydział MiNI PW -32-
WieŜe Hanoi (1) jeszcze na temat... funkcje rekurencyjne "wieŝe Hanoi" Plik Tower of Hanoi.jpeg znajduje się w Wikimedia Commons Tomasz Marks - Wydział MiNI PW -33- Tomasz Marks - Wydział MiNI PW -34- WieŜe Hanoi (2) Problem: Posługując się trzema słupkami, z których pierwszy zawiera wieŝę z krąŝków o malejących (w kierunku do góry) średnicach, a dwa pozostałe są puste: odbuduj, z zachowaniem kształtu, wieŝę na trzecim słupku. Warunek 1. Podczas przekładania krąŝków wolno się posługiwać buforem, reprezentowanym przez drugi słupek. Warunek 2. Nie wolno kłaść krąŝka o większej średnicy na mniejszy ani przekładać kilku krąŝków jednocześnie. Sytuacja początkowa: Sytuacja docelowa: WieŜe Hanoi (3) UWAGA: ZłoŜoność obliczeniowa tego zadania wzrasta bardzo szybko wraz ze zwiększaniem liczby elementów wieŝy. Tomasz Marks - Wydział MiNI PW -35- Powiedzmy, Ŝe opis rozwiązania zadania zrealizuje wykonanie funkcji Hanoi ( 5, A, B,C ) Tomasz Marks - Wydział MiNI PW -36-
WieŜe Hanoi (4) Aby przemieścić dolny krąŝek na słupek C, trzeba doprowadzić do sytuacji: WieŜe Hanoi (5) Po przemieszeniu dolnego krąŝka na słupek C Rozwiązanie tego 'mniejszego' zadania moŝna zapisać jako wykonanie funkcji Hanoi ( 4, A, C, B ) Po której moŝna przenieść pojedynczy krąŝek z A na C. Zapiszemy to symbolicznie: A C Teraz trzeba wykonać zadanie Hanoi ( 4, B, A, C ) aby doprowadzić do oczekiwanego stanu końcowego. Tomasz Marks - Wydział MiNI PW -37- Tomasz Marks - Wydział MiNI PW -38- Zatem wyjściowe zadanie WieŜe Hanoi (6) Hanoi ( 5, A, B, C ) MoŜna zapisać jako sekwencję: Hanoi ( 4, A, C, B ) A C Hanoi ( 4, B, A, C ) WieŜe Hanoi (7) Uogólniając dla zadanego n Hanoi ( n, A, B, C ) MoŜna zapisać jako sekwencję: Hanoi ( n-1, A, C, B ) A C Hanoi ( n-1, B, A, C ) Tomasz Marks - Wydział MiNI PW -39- Tomasz Marks - Wydział MiNI PW -40-
WieŜe Hanoi (8) MoŜemy zatem zdefiniować funkcję, która opisze nam kolejne przemieszczenia: void Hanoi ( int n, char A, char B, char C ) if (n > 0) Hanoi ( n-1, A, C, B ); printf ( "%c --> %c\n", A, C ); Hanoi (n-1, B, A, C ); WieŜe Hanoi (9) A cały program moŝe wyglądać tak: // hanoi_prog.c #include <stdio.h> void Hanoi ( int n, char A, char B, char C ) if (n > 0) Hanoi ( n-1, A, C, B ); printf ( "%c --> %c\n", A, C ); Hanoi (n-1, B, A, C ); void main ( void ) int N; scanf ( "%d", &N ); Hanoi ( N, 'A', 'B', 'C' ); Tomasz Marks - Wydział MiNI PW -41- Tomasz Marks - Wydział MiNI PW -42- WieŜe Hanoi (10) Wyniki działania programu dla N == 4 : Koniec wykładu 12. Tomasz Marks - Wydział MiNI PW -43- Tomasz Marks - Wydział MiNI PW -44-