Lataa esitys
Esittely latautuu. Ole hyvä ja odota
1
Tietorakenteet ja algoritmit
Rekursio Rekursion käyttötapauksia Rekursio määritelmissä Rekursio ongelmanratkaisussa ja ohjelmointitekniikkana Esimerkkejä taulukolla Esimerkkejä linkatulla listalla Hanoin tornit © Hannu Laine 11/9/2012
2
Rekursio Rekursio tarkoittaa ”palauttamista itseensä”.
Rekursiota käytetään Määritelmissä Ongelmanratkaisussa Ohjelmointitekniikkana (toisto) Funktio, joka kutsuu itseään on rekursiivinen © Hannu Laine 11/9/2012
3
Rekursio määritelmissä
Asioita voidaan määritellä rekursiivisesti. Esimerkiksi lista, binääripuu ja kertoma (n!) voidaan määritellä rekursiivisesti. Esimerkkinä kertoman rekursiivinen määrittely. Matemaattinen käsite kertoma määritellään näin 1, kun n= 0 (rekursion kanta) n! = n*(n-1)!, kun n>0 (rekursiivinen osa) Seuraavalla sivulla sovelletaan kertoman määritelmää käytäntöön. © Hannu Laine 11/9/2012
4
Kertoman määritelmän käyttö
Käytetään edellisen sivun kertoman määritelmää ja selvitetään, mitä on 5! 5! = 5*(5-1)! = 5*4! = 5*(4*(4-1)!) = 5*(4*3!) = 5*(4*(3*(3-1)!)) = 5*(4*(3*2!)) = 5*(4*(3*(2*(2-1)!))) = 5*(4*(3*(2*1!))) = 5*(4*(3*(2*(1*(1-1)!)))) = 5*(4*(3*(2*(1*0!)))) = 5*(4*(3*(2*(1*1)))) = 5*(4*(3*(2*1))) = 5*(4*(3*2)) = 5*(4*6) = 5*24 = 120 Pienennetään probleemaa Kanta eli yksinkertainen tapaus löytynyt Palataan ”takaperin” ratkaisuun © Hannu Laine 11/9/2012
5
Rekursio ohjelmointitekniikkana
Rekursiivinen funktio kutsuu itse itseään. Esimerkki. Kertoman laskenta // Rekursiivinen funktio, joka laskee kertoman int factorial(int number) { if (number == 0) return 1; if (number > 0) return number*factorial(number-1); } void main (void) { printf("\nThe factorial of 4 is %d", factorial(4)); Iteratiivinen olisi tässä parempi. Esimerkki annettu periaatteen oppimisen tarkoituksessa. © Hannu Laine 11/9/2012
6
Rekursio ongelmanratkaisussa
Periaate 1. Etsitään yksinkertainen tapaus (ns.rekursion kanta) 2. Palautetaan ongelma itseensä, mutta kooltaan pienennettynä 3. Toistetaan kohtaa 2, kunnes päästään yksinkertaiseen tapauksen (kantaan) 4. Paluu "takaperin" lopulliseen ratkaisuun Esimerkkejä Seuraavaksi käsitellään erilaisia esimerkkejä, kuinka rekursiota käytetään ongelmanratkaisussa ja ohjelmoitaessa ongelman ratkaisevaa funktiota. Esimerkit ovat 1. Taulukon alkioiden tulostus käänteisessä järjestyksessä 2. Taulukon alkioiden järjestyksen kääntö 3. Linkatun listan alkioiden tulostus käänteisessä järjestyksessä 4. Linkatun listan loppuun lisääminen 5. Hanoin tornit © Hannu Laine 11/9/2012
7
Taulukon alkioiden tulostus käänteisessä järjestyksessä 1
Ajattelutapa Oletetaan, että taulukossa on n alkiota. Yksinkertainen tapaus: Taulukko on tyhjä eli siinä on 0 alkiota. Ratkaisu on yksinkertainen, koska ei tarvitse tulostaa tai tehdä mitään. Probleeman pienentäminen: Jos taulukko ei ole tyhjä eli n > 0. Tulostetaan käänteisessä järjestyksessä yhtä alkiota pienempi taulukko, joka seuraa ensimmäistä alkiota. Tämän jälkeen tulostetaan alkuperäisen taulukon ensimmäinen alkio. Asia ohjelmana seuraavalla sivulla. Sama alkuperäinen ongelma mutta pienempänä © Hannu Laine 11/9/2012
8
Taulukon alkioiden tulostus käänteisessä järjestyksessä 2
void print_array(const int* arr, int n); void print_in_reverse_order(const int* arr, int n); void main(void) { int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; print_array(array, 10); print_in_reverse_order(array, 10); } void print_in_reverse_order(const int* arr, int n) { if ( n == 0) { //printf("\n"); Tulee alkuun return; else { print_in_reverse_order(arr + 1, n - 1); printf("%d ", *arr); Tarkastellaan, miten prosessori suorittaa tämän funktion. void print_array(const int* arr, int n) { const int *p; for (p = arr; p < arr + n; p++) printf("%d ", *p); printf("\n"); } © Hannu Laine 11/9/2012
9
Taulukon alkioiden järjestyksen kääntö 1
Ajattelutapa Oletetaan, että taulukossa on n alkiota. Yksinkertainen tapaus: Taulukko on tyhjä eli siinä on 0 alkiota. Ratkaisu on yksinkertainen, koska ei tarvitse tehdä mitään. Probleeman pienentäminen: Jos taulukko ei ole tyhjä eli n > 0. Käännetään alkioiden järjestys yhtä alkiota pienemmässä taulukossa, joka seuraa ensimmäistä alkiota. Sitten laitetaan talteen alkuperäisen taulukon ensimmäinen alkio. Seuraavaksi siirretään käännetty pienempi taulukko askel taaksepäin alkuperäisessä taulukossa. Lopuksi viedään talteen laitettu alkuperäisen taulukon ensimmäinen alkio taulukon viimeiseksi. Asia ohjelmana seuraavalla sivulla. Sama alkuperäinen ongelma mutta pienempänä © Hannu Laine 11/9/2012
10
Taulukon alkioiden järjestyksen kääntö 2
void invert_array(int *array, int n) { int aux; if (n == 0) return; else { aux = *array; invert_array(array+1, n-1); move_one_step_backwards(array, n-1); *(array+n-1) = aux; } void move_one_step_backwards(int *array, int n) { int i; for (i = 0; i < n; i++) array[i] = array[i+1]; Tarkastellaan, miten prosessori suorittaa tämän funktion. © Hannu Laine 11/9/2012
11
Rekursiivisia funktioita linkatulle listalle
Linkatun listan alkioiden tulostus käänteisessä järjestyksessä Tässä rekursio sopii, koska osoittimet on laitettava joka tapauksessa jonnekin talteen. Linkattu merkkilista on määritelty muodossa typedef Tpointer Tlist; Seuraava funktio tulostaa listan käännetyssä järjestyksessä. void print_list_in_reverse (Tlist list) { if (list == NULL) printf("\nList in reverse order :"); else { print_list_in_reverse (list->next); printf("%c ", list->item); } kanta (triviaali tapaus) probleeman ”pienentäminen” © Hannu Laine 11/9/2012
12
Kuinka rekursiivinen funktio toimii
Käydään läpi edellisen sivun funktion toiminta ja oletetaan että linkattu lista on muotoa: c 3000 1000 a 2000 b Luvut 1000, 2000 ja 3000 kuvaavat muistiosoitteita. 1000 3000 2000 NULL Pinon sisältö kun rekursion kanta saavutetaan. © Hannu Laine 11/9/2012
13
Muita rekursiivisia funktioita linkatulle listalle
Linkatun listan loppuun lisääminen insert_to_list_end Rekursiivinen ratkaisu voi olla myös tehottomampi kuin iteratiivinen (pinon ”haaskaus”). Linkattu merkkilista on määritelty muodossa typedef Tpointer Tlist; Rekursiivinen funktio, jolla lisätään alkio listan loppuun. void insert_to_list_end(Tlist *list, Titem data) { if (*list == NULL ) { *list = (Tpointer) malloc(sizeof(Tnode)); (*list) -> item = data; (*list) -> next = NULL;} else insert_to_list_end_1(&((*list)->next), data); } © Hannu Laine 11/9/2012
14
Rekursion oikea ja ”väärä” käyttö
Funktio print_list_in_reverse on esimerkki oikeasta tavasta käyttää rekursiota, koska siinä pinoon tallennettavaa ”historiaa” todella tarvitaan. Rekursion oikeaa käyttöä ei ole toteuttaa sillä vain toisto, koska funktion parametrit ja mahdolliset paikalliset muuttujat viedään joka kutsukerralla uudelleen pinoon ja näin haaskataan pinomuistia. © Hannu Laine 11/9/2012
15
Hanoin tornit Klassinen esimerkki rekursiosta probleemanratkaisussa: Hanoin tornit. Probleeman kuvaus: Kultalevyt sauvassa a on siirrettävä sauvaan c sauvaa b hyväksikäyttäen siten, että 1. Missään vaiheessa isompi levy ei ole pienemmän päällä missään sauvassa. 2. Vain yksi levy kerrallaan saa olla pois sauvoista. Käydään läpi ongelman rekursiivisen ratkaisun ajatteluperiaate alla olevan kuvan avulla. Ratkaisun periaate: 1. siirretään n-1 levyä sääntöjä noudattaen tangosta a tankoon b käyttäen hyväksi tankoa c 2. siirretään tankoon a jäänyt suurin levy tankoon c 3. siirrettään n-1 levyä sääntöjä noudattaen tangosta b tankoon c käyttäen hyväksi tankoa a © Hannu Laine 11/9/2012
16
Hanoin tornien ongelman ratkaiseva ohjelma
#include <stdio.h> void siirra (int n, char tanko1, char tanko2, char tanko3) { // seuraavilla riveillä voitaisiin testata, montako kertaa funktiossa on käyty // static int kerta = 0; // printf(”Kerta := %d n = %d\n", kerta++, n); if (n==1) printf("\n Siirrä tangosta %c tankoon %c ", tanko1, tanko3); else { siirra(n - 1, tanko1, tanko3, tanko2); siirra(n - 1, tanko2, tanko1, tanko3); } void main (void) { int n; printf("\n Montako levyä :"); scanf("%d", &n); printf("\n Käytä seuraavia siirtoja "); siirra(n, 'a', 'b', 'c'); © Hannu Laine 11/9/2012
17
Hanoin tornien kutsupuu
© Hannu Laine 11/9/2012
18
Rekursion, pinon ja puiden yhteydet.
Rekursio, pino ja puut ovat kiinteästi yhteen “kietoutuneita”. Rekursion toteuttamiseen käytetään pinoa. Rekursiivisen funktion, jossa funktio kutsuu itseään kaksi kertaa, kutsuista muodostuu ns. kutsupuu. Tietorakenne puu määritellään rekursiivisesti. Useimmat puita käsittelevät operaatiofunktiot toteutetaan rekursiivisesti. Puihin tutustutaan paremmin osassa 13 © Hannu Laine 11/9/2012
19
Lisää rekursiolle sopivia probleemoita
Sivulla 6 mainittiin eräitä yksinkertaisia probleemoita, jotka voidaan ratkaista rekursiivisesti, vaikka niiden iteratiivinen ratkaisukin on melko suoraviivainen. Seuraavassa esimerkkejä, joissa rekursio yksinkertaistaa huomattavasti ratkaisua: Infix-lausekkeen muuntaminen postfix-muotoon Ruudukossa olevan mielivaltaisen yhtenäisen tahran koon laskeminen (labratehtävänä) Puumaisen hakemistorakenteen läpikäynti Reitin etsintä verkosta Monet binäärisen etsintäpuun operaatiofunktiot Jne Muista myös mitä sivulla 14 on sanottu © Hannu Laine 11/9/2012
Samankaltaiset esitykset
© 2024 SlidePlayer.fi Inc.
All rights reserved.