Esittely latautuu. Ole hyvä ja odota

Esittely latautuu. Ole hyvä ja odota

Tietorakenteet ja algoritmit

Samankaltaiset esitykset


Esitys aiheesta: "Tietorakenteet ja algoritmit"— Esityksen transkriptio:

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


Lataa ppt "Tietorakenteet ja algoritmit"

Samankaltaiset esitykset


Iklan oleh Google