Algoritmit ja tietorakenteet Copyright Hannu Laine. 1, kun n= 0. n*(n-1)!, kun n>0;



Samankaltaiset tiedostot
Tietorakenteet ja algoritmit

Matematiikan tukikurssi

Epäyhtälön molemmille puolille voidaan lisätä sama luku: kaikilla reaaliluvuilla a, b ja c on voimassa a < b a + c < b + c ja a b a + c b + c.

Johdatus diskreettiin matematiikkaan Harjoitus 7,

Luonnollisten lukujen laskutoimitusten määrittely Peanon aksioomien pohjalta

Lisää segmenttipuusta

2.2 Täydellinen yhtälö. Ratkaisukaava

Eksponenttifunktion Laplace muunnos Lasketaan hetkellä nolla alkavan eksponenttifunktion Laplace muunnos eli sijoitetaan muunnoskaavaan

Matematiikan tukikurssi

MS-A Matriisilaskenta Laskuharjoitus 3

Tietorakenteet ja algoritmit

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

Luku 6. Dynaaminen ohjelmointi. 6.1 Funktion muisti

Luento 6. June 1, Luento 6

String-vertailusta ja Scannerin käytöstä (1/2) String-vertailusta ja Scannerin käytöstä (2/2) Luentoesimerkki 4.1

2.7 Neliöjuuriyhtälö ja -epäyhtälö

OHJ-1151 Ohjelmointi IIe

Algoritmit 2. Luento 8 Ke Timo Männikkö

Oletetaan, että funktio f on määritelty jollakin välillä ]x 0 δ, x 0 + δ[. Sen derivaatta pisteessä x 0 on

Aluksi Kahden muuttujan lineaarinen epäyhtälö

Induktio kaavan pituuden suhteen

Tietorakenteet ja algoritmit

Sähköpostiohjeet. Tehokas ja huoleton sähköposti

Matematiikan tukikurssi 3.4.

MAA10 HARJOITUSTEHTÄVIÄ

Käyttöjärjestelmät: Virtuaalimuisti

Massaeditorikoulutus KANSALLISKIRJASTO - Kirjastoverkkopalvelut

Luku 3. Listankäsittelyä. 3.1 Listat

monissa laskimissa luvun x käänteisluku saadaan näyttöön painamalla x - näppäintä.

EUROOPAN YHTEISÖJEN KOMISSIO. Ehdotus: NEUVOSTON ASETUS. neljännesvuosittaista julkista velkaa koskevien tietojen laatimisesta ja toimittamisesta

Ilmoittautuminen kansalliseen, SM-, AM- tai avoimeen kilpailuun

Tietorakenteet ja algoritmit

Esimerkki 8. Ratkaise lineaarinen yhtälöryhmä. 3x + 5y = 22 3x + 4y = 4 4x 8y = r 1 + r r 3 4r 1. LM1, Kesä /68

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

Algoritmit 2. Luento 14 Ke Timo Männikkö

Tietorakenteet ja algoritmit syksy Laskuharjoitus 1

Ohjelmoinnin peruskurssi Y1

Esimerkkejä derivoinnin ketjusäännöstä

Algoritmit 2. Luento 7 Ti Timo Männikkö

Algoritmit 2. Luento 13 Ti Timo Männikkö

TW- EAV510: WDS- TOIMINTO KAHDEN TW- EAV510 LAITTEEN VÄLILLÄ

Algoritmit 1. Luento 3 Ti Timo Männikkö

A TIETORAKENTEET JA ALGORITMIT

Tietorakenteet ja algoritmit

Funktion raja-arvo 1/6 Sisältö ESITIEDOT: reaalifunktiot

Optimointi. Etsitään parasta mahdollista ratkaisua annetuissa olosuhteissa. Ongelman mallintaminen. Mallin ratkaiseminen. Ratkaisun analysointi

Tietorakenteet ja algoritmit

Algoritmit 2. Luento 8 To Timo Männikkö

A TIETORAKENTEET JA ALGORITMIT

Muuttujien roolit Kiintoarvo cin >> r;

811120P Diskreetit rakenteet

Muita linkattuja rakenteita

Tietorakenteet (syksy 2013)

11.4. Rakenteellista käsittelyä tilavuusrenderöintialgoritmeissa

Johdatus yliopistomatematiikkaan, 2. viikko (2 op)

Numeeriset menetelmät

Racket ohjelmointia I

Matematiikan tukikurssi

Teen koko ajan aktiivista mainontaa Googlessa. Tavoite on olla etusivulla, kun haetaan henkisiä tapahtumia, kursseja, yrittäjiä.

JOENSUUN SEUDUN HANKINTATOIMI KOMISSIOMALLI

30 + x ,5x = 2,5 + x 0,5x = 12,5 x = ,5a + 27,5b = 1,00 55 = 55. 2,5a + (30 2,5)b (27,5a + 27,5b) =

Lue ohjeet huolellisesti ennen laitteen käyttöä.

Merkintöjen tekeminen pohjakuvaan Libre Officella v.1.2

Tietorakenteet ja algoritmit. Kertaus. Ari Korhonen

Ravintovartti, teemana lautasmalli

Ohjelmoinnin perusteet Y Python

- Kommentoi koodisi. Koodin kommentointiin kuuluu kuvata metodien toiminta ja pääohjelmassa tapahtuvat tärkeimmät toiminnat. Esim.

Moodle HOPS-työskentelyn tukena

E-kirjat. ja uusi Ellibsin käyttöliittymä

Merkkien ja merkkijonojen käsittelyä Javalla

TILASTOLLINEN LAADUNVALVONTA

Racket ohjelmointia. Tiina Partanen 2014

(x 0 ) = lim. Derivoimissääntöjä. Oletetaan, että funktiot f ja g ovat derivoituvia ja c R on vakio. 1. Dc = 0 (vakiofunktion derivaatta) 2.

Diskreetit rakenteet

Algoritmit 1. Luento 1 Ti Timo Männikkö

Helmi-koulutus Tuen lomakkeet

58131 Tietorakenteet ja algoritmit (syksy 2015)

Miten yliopiston teoriaopetus vastaa harjoittelussa tarvittaviin taitoihin? Opetusapteekkien neuvottelupäivä Liisa Niemi

1 / 11. Digitaalisen arkkitehtuurin yksikkö Aalto-yliopisto. Pikaopas Maxwelliin. ARK-A2500 DA-alkeet Elina Haapaluoma, Heidi Silvennoinen Syksy 2015

Lyhyt kertaus osoittimista

Kenguru 2016 Mini-Ecolier (2. ja 3. luokka) Ratkaisut

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

C-ohjelma. C-ohjelma. C-ohjelma. C-ohjelma. C-ohjelma. C-ohjelma. Operaatioiden suoritusjärjestys

Hae Opiskelija käyttöohje

Tarjoajalla on oltava hankinnan kohteen laatu ja laajuus huomioon ottaen kokemusta seuraavilla alueilla:

TIETORAKENTEET JA ALGORITMIT

Sisältö Yleistä. Esittely ja luominen. Alkioiden käsittely. Kaksiulotteinen taulukko. Taulukko metodin parametrina. Taulukko ja HelloWorld-ohjelma. Ta

Se mistä tilasta aloitetaan, merkitään tyhjästä tulevalla nuolella. Yllä olevassa esimerkissä aloitustila on A.

( ) ( ) ( ) ( ( ) Pyramidi 4 Analyyttinen geometria tehtävien ratkaisut sivu 271 Päivitetty a) = keskipistemuoto.

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

Asia T-237/00. Patrick Reynolds vastaan Euroopan parlamentti

Algoritmit 1. Demot Timo Männikkö

Web-teknologiat. XML-datan kysely Topi Sarkkinen

SMG-4200 Sähkömagneettisten järjestelmien lämmönsiirto Ehdotukset harjoituksen 4 ratkaisuiksi

Algoritmit 1. Luento 12 Ti Timo Männikkö

- Valitaan kohta Asetukset / NAT / Ohjelmallinen palvelin - Seuraavassa esimerkki asetuksista: valitaan käytössä oleva ohjelmistorajapinta

Tietorakenteet, laskuharjoitus 3, ratkaisuja

TimeEdit henkilökunnan ohje

Laboratoriotyön sisältö. Pareittain tehtävä laboratoriotyö Vaatimukset: Laboratoriotyöskentely Loppuraportti (1 raportti/työ)

Transkriptio:

1 Rekursio Rekursion periaate ja rekursio määrittelyvälineenä Rekursiota käytetään tietotekniikassa ja matematiikassa erilaisiin tarkoituksiin. Eräänä käyttöalueena on asioiden määrittely. Esimerkkinä käsitellään luvun kertoman määrittelyä. Luvun n kertomaa merkitään n!, ja se tarkoittaa lukujen 1, 2, 3,..., n tuloa. Tässä määrittelyssä tarvitaan epämääräistä kolmen peräkkäisen pisteen merkintää. Epämääräisyydestä päästään eroon käyttämällä rekursiivista määritelmää kertomalle. Kertoma määritellään rekursiivisesti seuraavalla tavalla n! = 1, kun n= 0 n*(n-1)!, kun n>0; Aluksi voi tuntua siltä, että määritelmässä pyöritään kehää, koska siinä on käytetty kertoman määritelmää. Määritelmässä sanotaan, että n:n kertoma on n kertaa n-1:n kertoma. Asian ydin piilee ensinnäkin siinä, että määritelmässä on yksi tapaus, jossa kertoma on määritelty yksikäsitteisesti. Se on tapaus n = 0. Määritelmän mukaan luvun 0 kertoma on 1. Tätä osaa määritelmässä kutsutaan rekursion kannaksi. Toinen tärkeä piirre määritelmässä on, että rekursiivisessa osassa kertoma otetaan pienemmästä luvusta kuin alkuperäinen. Tämän seurauksena, kun rekursiota sovelletaan toistuvasti, saavutetaan lopulta rekursion kanta. Kuinka sitten määrittelyä voidaan käyttää käytännössä tietyssä tapauksessa? Yritetään selvittää kertoman määritelmän perusteella, mitä on 5!. Arvo saadaan määritelmän mukaan asteittain seuraavasti: Näin saadaan, että askel 1 Onko 5 > 0? On --> 5! = 5*(5-1)! = 5*4! askel 2 Onko 4 > 0? On --> 4! = 4*(4-1)! = 4*3! askel 3 Onko 3 > 0? On --> 3! = 3*(3-1)! = 3*2! askel 4 Onko 2 > 0? On --> 2! = 2*(2-1)! = 2*1! askel 5 Onko 1 > 0? On --> 1! = 1*(1-1)! = 1*0! askel 6 Onko 0 > 0? Ei -->0! = 1. 5! = 5*(4*(3*(2*(1*1)))) Määritelmän rekursiivista osaa on käytetty aina uudelleen ja uudelleen. Joka kerralla kuitenkin luku, jonka kertomaa ratkaistaan, on pienempi kuin edellisellä kerralla. Lopuksi luku on niin pieni, että määrittelyssä voidaan käyttää sen ei-rekursiivista osaa eli kantaa. Tällöin rekursio päättyy. Vasta rekursion päättymisen jälkeen voidaan kertomalle laskea arvoa. Laskenta tapahtuu tällöin takaperin, kuten lausekkeesta nähdään. Ensin lasketaan siis 1*1 = 1, sitten 2*1 = 2, sitten 3*2=6, sitten 4*6=24 ja lopuksi 5*24= 120. Tässä on esitelty, kuinka rekursiota voidaan käyttää määrittelyssä. Tietotekniikassa esimerkiksi puurakenteiden määrittelyssä käytetään yleensä rekursiivista määrittelyä. Rekursiota käytetään myös ongelman ratkaisumenetelmänä. Rekursio voidaan toteuttaa myös ohjelmoinnissa, jolloin sitä voidaan pitää lisäksi yhtenä ohjelmointitekniikkana. Edellä esitettiin rekursion käyttö määrittelyssä. Siinä tuli samalla esille rekursion tärkeimmät ominaisuudet yleisesti. Rekursiossa asia palautetaan itseensä, kuitenkin niin, että asia pienenee (edellä kertoma otetaan pienemmästä luvusta). Kun palautus itseensä tehdään

2 riittävän monta kertaa, asia on tullut niin pieneksi, että sen ratkaisu on itsestään selvä tai hyvin yksinkertainen. Kun tämä vaihe on saavutettu, rekursio päättyy ja tehtävässä palataan takaperin alkuun siten, että kun kantatapaus on ratkennut, voidaan selvittää kantatapausta edeltänyt vähän suurempi tapaus. Kun tämä on ratkennut, voidaan ratkaista sitä edeltänyt suurempi tapaus. Näin palataan lopulta alkuperäisen asian tai tehtävän ratkaisuun. Toisena esimerkkinä rekursiivisesta määritelmästä käsitellään tunnilla järjestettyjen alkioiden joukko eli sarja (sequence), joka oli perustana lineaarisen listan määrittelyssä. Rekursion käyttö ongelmanratkaisussa On monia sellaisia probleemoita, joiden ratkaiseminen on ilman rekursiota erittäin hankalaa, mutta rekursiivisella ajattelumallilla hyvin yksinkertaista. Perinteisenä esimerkkinä tällaisesta on ns. Hanoin tornien probleema. Siinä on kolme pystyssä olevaa tankoa (a, b ja c).tankoon a on pujotettu n kappaletta erikokoisia levyjä, joissa on reikä keskellä. Levyt ovat kaikki erikokoisia. Levyt on pujotettu tankoon siten, että suurin levy on alimmaisena ja pienin päällimmäisenä ja jokainen levy on aina alla olevaa pienempi. Tilanne on siis alussa alla olevan kuvan mukainen. a b c Tehtävänä on siirtää kaikki levyt tangosta a tankoon c. Tankoa b saadaan käyttää apuna. Siirroissa on noudatettava seuraavia sääntöjä: 1. Vain yksi levy saa olla kerrallaan pois tankoista. 2. Missään tangossa ei saa missään tilanteessa olla levyjä siten, että suurempi levy olisi pienemmän päällä. Probleeman ratkaisu on yleisellä tasolla hankala. Tämä johtuu siitä, että tarvittavien siirtojen määrä kasvaa eksponentiaalisesti levyjen määrän kasvaessa. Rekursiota käyttäen saadaan helposti algoritmi, joka kertoo tarvittavat siirrot. Ratkaisu on yksinkertainen (mutta tarvittavien siirtojen määrä tietysti edelleen suuri). Etsitään ensin sellainen pieni ongelma, joka on yksinkertainen. Tässä sellaisena voidaan pitää tapausta, jossa levyjä on vain yksi. Tällöinhän kaikki säännöt toteuttava ratkaisu on sellainen, että siirretään levy suoraan tangosta a tankoon c. Ratkaisun rekursiivisessa osassa on ilmaistava, miten siirretään n levyä. Rekursiivisessa osassa lähdetään siitä, että tehtävä osataan hoitaa yhtä pienemmälle levymäärälle n-1. Jos siis levyjä olisi n kappaletta, ne voidaan siirtää sääntöjä noudattaen tankoon c seuraavasti siirretään n-1 levyä sääntöjä noudattaen tangosta a tankoon b käyttäen hyväksi tankoa c 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 Kuinka n-1 levyä siirretään sääntöjä noudattaen, on siis edelleen avoin kysymys. Rekursion mukaan n-1 levyn siirtäminen voidaan edelleen jakaa kahteen osaan siten, siirretään n-2 levyä ensin pois päältä sääntöjä noudattaen samalla tavalla kuin edellä. Näin ongelma pienenee jokaisella kerralla, ja lopulta se palautuu yksinkertaisimpaan tapaukseen, jossa siirrettäviä levyjä on yksi. Tämän jälkeen tarvittavat siirrot saadaan selville takaperin. Rekursiivinen funktio Ohjelmointikielissä voidaan toteuttaa rekursiivinen funktio. Rekursiivisella funktiolla tarkoitetaan funktiota, joka kutsuu itse itseään. Yllä kuvatusta Hanoin tornien probleeman ratkaisusta voidaan kirjoittaa funktio, joka noudattaa suoraan ongelmanratkaisussa käytettyä ajattelumallia. Ohjelma näyttää seuraavalta. /* Hanoin tornit. Esimerkki valaisee rekursion käyttöä probleeman ratkaisussa ja vastaavan ohjelman kirjoituksessa.*/ #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("\nkerta := %d n = %d", kerta++, n); if (n==1) printf("\n Siirrä tangosta %c tankoon %c ", tanko1, tanko3); else { siirra(n - 1, tanko1, tanko3, tanko2); printf("\n Siirrä tangosta %c tankoon %c ", tanko1, tanko3); 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'); Tunnilla käsitellään ohjelman suorituksen yhteydessä syntyvä ns. kutsupuu. Toisena sovellusesimerkkinä käsitellään tunnilla aikaisemmin esitetty tehtävä, jossa dynaamisesti linkatun listan loppuun lisätään uusi alkio, kun listasta tiedetään vain sen alkuosoite. Rekursiivisen funktion suoritusperiaate prosessorissa Rekursiivinen funktio on funktio, joka kutsuu itseään. Kun funktio kutsuu itseään, sen parhaillaan menossa oleva suoritus jää kesken. Kun seuraavalla kierroksella funktio kutsuu taas itseään, jää sen suoritus taas kesken kutsun kohdalla. Kun lopulta saavutetaan rekursion kanta, pääsee funktio tilanteeseen, jossa sillä hetkellä menossa oleva suoritus päästään loppuun. Tämän jälkeen funktio muistaa kaikki kesken jääneet suoritukset. Kannan

4 suorituksen jälkeen suoritetaan kantaa edeltäneen kutsun suoritus loppuun siitä kohdasta eteenpäin, jossa suoritus keskeytyi eli kutsua seuraavasta kohdasta. Tällä periaatteella kaikki kesken jääneet funktion suoritukset viedään loppuun siten, että viimeisenä suoritetaan loppuosa ensimmäisenä tehdystä funktion kutsusta. Kaikki kesken jääneet funktiokutsut suoritetaan siis takaperin loppuun. Rekursiivisen funktion toiminta perustuu siihen, että funktion kutsun yhteydessä parametrit, paluuosoite ja paikalliset muuttujat viedään pinoon.. Näin tapahtuu jokaisella kutsukerralla. Pinossa on siis erikseen kaikki edellä mainitut tiedot kutakin kutsukertaa varten. Koska tiedot ovat pinossa, tapahtuu funktioiden suoritus loppuun takaperin. Tiedothan löytyvät pinosta käänteisessä järjestyksessä pinon luonteen takia. Seuraavassa tarkastellaan esimerkkiä, jossa dynaamisesti varatun linkatun listan loppuun lisätään alkio rekursiivisella funktiolla insert_to_list_end. Kysymys on tapauksesta, jossa listasta on tiedossa vain sen alkuosoite. Tällöin listan loppu on etsittävä. Listan loppu voidaan luonnollisesti etsiä tavallista iteratiivista menettelyä käyttäen, jossa siirrytään listassa eteenpäin kunnes tulee vastaan alkio, jossa next-kentässä on arvo NULL. Iteratiivinen ratkaisu on jopa tehokas ja hyvä ratkaisu. Tässä esitetään kuitenkin rekursiivinen ratkaisu, koska se havainnollistaa erittäin hyvin rekursiivisen ajattelun periaatetta ja sillä voidaan kätevästi demonstroida miten prosessori suorittaa rekursiivista funktiota. Listan loppuun lisäämisen rekursiivinen ajattelumalli voidaan johtaa sarjan rekursiivisesta määrittelystä. Sarjan rekursiivisen määritelmän mukaan tyhjä joukko on sarja (kanta). Jos L on sarja ja a on alkio, niin al on sarja. Lineaarisen listan dynaamisesti linkatussa esityksessä solmu, joka sisältää alkion a, sisältää myös seuraavan solmun osoittimen. Tällöin voidaan ajatella, että ensimmäisen solmun next kenttä esittää listaa L, jossa on kaikki loput alkiot, samalla tavalla kuin ensimmäisen solmun osoite esittää koko listaa. Tämän ajattelumallin avulla alkion lisääminen listan loppuun voidaan esittää rekursiivisella ajattelumallilla seuraavasti: lisää alkio listan ensimmäiseksi alkioksi, jos lista on tyhjä muussa tapauksessa lisää alkio sen listan loppuun, joka seuraa ensimmäistä alkiota Rekursiivinen ratkaisu täyttää rekursion vaatimukset, koska sillä on kanta, jossa ratkaisu on selkeä, alkion lisääminen tyhjään listaan. Toisaalta ratkaisun rekursiivinen osa kohdistuu pienempään ongelmaan kuin alkuperäinen, koska siinä alkion lisäys tapahtuu yhtä alkiota lyhyempään listaan. Tämän takia rekursiivista osaa toistettaessa, lista lyhenee joka kierroksella ja lopuksi siitä tulee tyhjä lista, jolloin ratkaisu on kannan mukainen. Rekursiivinen funktio insert_to_list_end on esitetty dynaamisten rakenteiden käsittelyn yhteydessä (esimerkkitapaus case 5). Seuraavassa on tämä sama funktio. /* 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);

5 Ratkaisu on selkeä, ja se kuvaa rekursiivista ajattelua hyvin. Funktio ei kuitenkaan ole tehokkuuden kannalta paras mahdollinen, sillä kaikkien alkioiden osoitteet jäävät pinoon, kunnes kanta on saavutettu. Pinosta siis kulutetaan tilaa yhtä monelle osoitteelle, kuin listassa on alkioita. Tässä tehtävässä näitä osoitteita ei tarvita. Ainoana tavoitteenahan oli löytää viimeinen alkio listassa. Tunnilla käydään läpi tarkasti eri vaiheet tämän rekursiivisen funktion suorituksessa. Optimaalista rekursiota edustaa funktio, joka tulostaa dynaamisesti linkatun listan alkiot päinvastaisessa järjestyksessä. Lukija voi miettiä tehtävää ja kirjoittaa funktion ja kokeilla sitä aikaisemmin käsitellyllä listaohjelmalla. 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. Rekursiolle sopivia probleemoita Rekursiivinen ajattelu soveltuu mitä moninaisimpiin probleemoihin. Muutamia esimerkkejä ovat: Infix-lausekkeen muuntaminen postfix-muotoon Ruudukossa olevan mielivaltaisen yhtenäisen tahran koon laskeminen Puumaisen hakemistorakenteen läpikäynti.