Tietorakenteet ja algoritmit Pino Pinon määritelmä Pinon sovelluksia Järjestyksen kääntäminen Palindromiprobleema Postfix-lausekkeen laskenta Infix-lausekkeen muunto postfix-lausekkeeksi Sisäkkäiset funktiokutsut Backtracking menetelmä Pinon toteutustapoja Taulukko indeksoinnilla Taulukko osoittimilla 1
Pinon määritelmä (Stack) Määritelmä. Pino on järjestettyjen alkioiden kokoelma, jossa lisäykset tapahtuvat aina loppuun ja samoin alkion otto on mahdollista vain lopusta. Tässä tapauksessa loppua kutsutaan pinon huipuksi (top). Pinon tapauksessa alkioiden järjestyksen määrää syöttöjärjestys. Pinon operaatiot ovat: initialize (alustaa pinon) push (vie alkion pinon päälle) pop (ottaa alkion pinon päältä) is_empty (testaa, onko pino tyhjä) Vastaavat funktioiden prototyypit voisivat olla void initialize_stack (Tstack *pstack); Tboolean push( Tstack *pstack, Titem item); Tboolean pop( Tstack *pstack, Titem *pitem); int is_empty(const Tstack* pstack); void print_stack (const Tstack *pstack); // apufunktio testaukseen 2
Pinon sovelluksia Järjestyksen kääntäminen Palindromiprobleeman ratkaisu Laitteen purku ja kasaus Postfix-merkintäisen lausekkeen laskenta Infix-merkintäisen lausekkeen muunto postfixmerkintäiseksi Päällekkäisten menuikkunoiden toteutus käyttöliittymässä Sisäkkäiset funktiokutsut Sisäkkäiset keskeytyspalvelufunktiot Yksinkertainen rivieditori 3
Esimerkki1. Merkkijonon merkkien järjestyksen kääntö #include stack.h #include <string.h> void kaanna_jarjestys(char* mrk_jono) { Tstack merkkipino; int i, pit; initialize_stack(&merkkipino); for ( i = 0; i < strlen(mrk_jono), i++ push(&merkkipino, mrk_jono[i]); for ( i = 0; i < strlen(mrk_jono), i++ pop(&merkkipino, &mrk_jono[i]); } 4
Esimerkki 2. Palindromiprobleema (periaate) #include stack.h int onko_palindromi(const char* mrk_jono) { Tstack pino1, pino2, pino3; char mrk1, mrk2, mrk3; // initialisoi kaikki kolme pinoa // vie merkkijonon merkit pinoihin 1 ja 2 // siirrä merkit pinosta 2 pinoon 3 while(!is_empty(&pino2) { pop(&pino2, &mrk); push(&pino3, mrk); } // Otetaan tasatahtiin pinoista 1 ja 3 while(!is_empty(&pino1) { pop(&pino1, &mrk1); pop(&pino3, &mrk2); if(mrk1!= mrk2) return 0; } return 1; } 5
Infix-merkintäisen lausekkeen laskenta Infix-merkintäisen lausekkeen laskenta koneellisesti ei ole helppo tehtävä. Tehtävän tekee ongelmalliseksi se, että operaatioita ei voi suorittaa sitä mukaa, kun ne tulevat vastaan. Asioita on pidettävä muistissa ja sitten operaatioita tehtävä niiden vahvuusjärjestyksessä. Ihmiselle tehtävä on melko helppo. Ihminen skannaa lausekkeen läpi ja löytää helposti paikan, josta aloitetaan ja etenee oikeassa järjestyksessä havainnoiden koko ajan koko lauseketta. Esimerkiksi lauseketta 3 + 5 * 2 / 3 alusta tulkittaessa ei voida tehdä mitään operaatiota ennen kuin lausekkeessa on edetty viisi askelta! (3 operandia ja 2 operaattoria). Tehtävän koneellinen ratkaisu yksinkertaistuu oleellisesti, kun se jaetaan kahteen vaiheeseen: 1. infix-merkintäisen lausekkeeen muunto postfix-merkintäiseksi 2. postfix-merkintäisen lausekkeen laskenta Molemmissa vaiheissa tarvitaan pinoa muistivarastona! Tarkastellaan näitä vaiheita erikseen ja aloitetaan jälkimmäisestä 6
Postfix-merkintäinen lauseke RPN-laskimissa käytetään tällaista laskentatapaa. RPN-lyhenne tulee sanoista Reverse Polish Notation (eli käänteinen puolalainen merkitsemistapa) Postfix merkintä tarkoittaa, että operaattori tulee perässä operandien jälkeen. Lausekkeen laskenta koneellisesti on yksinkertaista. Myös lausekkeen merkintä on yleensä lyhyempi, koska sulkeita ei tarvita laskentajärjestyksen määräämiseksi. On myös tällä periaatteella toimivia ohjelmointikieliä, esimerkiksi PostScript-kieli. Esimerkki lausekkeesta: 3 5 + 7 2 - * vastaa infix merkintäistä lauseketta (3 + 5) * (7 2) 7
Postfix-merkintäisen lausekkeen laskenta Kuten edellisellä sivulla jo sanottiin, postfix-merkintäisen lausekkeen laskenta koneellisesti on helppoa. Lausekkeen laskenta perustuu operandien pinoon. Se on helppoa siksi, että lauseketta voidaan edetä vasemmalta oikealle ja päätös siitä mitä seuraavaksi tehdään, voidaan tehdä aina välittömästi. Käsittelysäännöt ovat tässä: 1) Kun lausekkeesta löytyy operandi, se viedään aina pinoon 2) Kun lausekkeesta löytyy operaattori, pinosta otetaan kaksi operandia ja niille tehdään operaattorin määräämä operaatio. Tulos viedään pinoon 3) Lopuksi pinossa on vain yksi arvo, joka on lopputulos. Taululla käydään läpi kuinka esimerkiksi lauseke 3 5 + 7 2 - * lasketaan (vastaten lauseketta (3 + 5) * (7 2) ). 8
Infix-merkintäisen lausekkeen muunto postfix-merkintäiseksi Myös infix-lausekkeen muuntaminen postfix-muotoon on koneellisesti helppoa, kun käytetään operaattorien pinoa. Nytkin lauseketta voidaan edetä vasemmalta oikealle ja päätös siitä mitä seuraavaksi tehdään, voidaan tehdä aina välittömästi. Käsittelysäännöt ovat: 1) Kun infix-lausekkeesta löytyy operandi, se viedään suoraan postfixlausekkeeseen. 2) Kun infix-lausekkeesta löytyy operaattori, se viedään aina pinoon, mutta sitä ennen pinosta otetaan pois ja viedään postfixlausekkeeseen kaikki operaattorit, joilla on korkeampi tai sama prioriteetti. 3) Lopuksi pinosta viedään postfix-lausekkeeseen kaikki siellä olevat operaattorit. Taululla käydään läpi kuinka esimerkiksi lauseke (3 + 5) * (7 2) muunnetaan postfix-muotoon. 9
Sisäkkäiset funktiokutsut Tietokoneessa käytetään pinomuistia (stack segment) funktioiden parametrien ja paikallisten muuttujien tallennuspaikkana. Se tekee mahdolliseksi sisäkkäiset funktiokutsut. Esimerkki int main(void) { int loc; f1(loc); } void f1(int par1) { int loc1; f2(loc1); } void f2(int par2) { int loc2; } f2 f1 main Stack segment loc2 par2 loc1 par1 loc 10
Yksinkertainen taulukkototeutus indeksoinnilla #define N 8 typedef... Titem; typedef struct { Titem array[n]; int top; } Tstack; Täydellinen toteutus verkossa (Esimerkki 1, Moniste 4) Edut: Yksinkertainen Havainnollinen Tehokas Tstack array top b a 1 7 6 3 45 2 1 0 11
Yksinkertainen taulukkototeutus osoittimilla #define N 8 typedef... Titem; typedef struct { Titem array[n]; Titem* top; Titem* maxpointer; Titem* minpointer; } Tstack; Täydellinen toteutus verkossa (Esimerkki 3, Moniste 4) Edut: Yksinkertainen Havainnollinen Vielä tehokkaampi Tstack array maxpointer top minpointer b a 7 6 3 45 2 1 0 12
Yleisyys Palautetaan mieleen, että säiliön toteutus on riippumaton säilöttävän alkion tyypistä. Myös nähdyt pinon toteutukset ovat sourcekooditasolla riippumattomia pinottavien alkioiden tyypistä! 13
Backtracking menetelmä Käytämme backtracking menetelmän esittelyssä esimerkkinä reitinhakuohjelmaa tieverkossa. Alla on tieverkon kuvaus graafisesti ja taulukkoesityksenä. 1 4 2 3 7 8 9 5 6 10 11 12 13 14 15 16 Ohjelma, joka etsii reitin annetusta solmupisteestä toiseen annettuun solmupisteeseen löytyy verkosta. 17 18 19 20 21 int graph[][2] ={ { 1,2},{1,3},{1,4}, {2,5},{2,6}, {3,7}, {4,8}, {4, 9}, {5,-1}, {6,10},{6,11}, {7,12},{7,13}, {8,-1}, {9,14},{9,15},{9,16}, {10,-1}, {11,17},{11,18}, {12,-1}, {13,-1}, {14,19},{14,20}, {15,21}, {16,-1}, {17,-1},{18,-1},{19,-1},{20,-1},{21,-1} }; 14