1. Johdanto Tietojenkäsittelytieteen olennaisimpia osia on abstraktio: Käsiteltävään ongelmaan on löydettävä sopiva malli, jota voidaan käsitellä tietokoneella. Malliin liittyvät ongelmaan sopivat tietorakenteet ja algoritmit, jotka käsittelevät tietorakenteita tuottaen ratkaisun ongelmaan. Tässä kurssissa perehdytään tavallisimpiin tietorakenteisiin ja niitä käsitteleviin algoritmeihin. Olennaista on oppia analysoimaan algoritmeja; analyysi sisältää sekä oikeellisuustarkasteluja että suorituskyvyn arviointia. Lisäksi tutustutaan joihinkin algoritmien suunnitteluparadigmoihin. Tärkein lähdeteos on Cormen, T.H., Leiserson, C.E., Rivest, R.L., Stein, C. Introduction to Algorithms ([Cor]), johon leijonanosa kurssin sisällöstä perustuu. Kurssille osallistuvan tietojenkäsittelytieteiden opiskelijan edellytetään hallitsevan soveltuvin osin kurssin Diskreetit rakenteet (811120P) asiat. Tässä osassa käsitellään algoritmeja ja niiden analysointia yleensä sekä esitellään kurssin kuluessa tarvittavia käsitteitä ja merkintöjä. 1.1. Algoritmeista ja tietorakenteista Algoritmin käsite on peräisin 800-luvulla eläneen matemaatikon Muhammad ibn Mūsā al- Khwārizmīn teoksesta al-kitāb al-mukhtaşar fī hisāb al-jabr wa-l-muqābala, jossa kuvataan ensimmäistä kertaa systemaattisesti ensimmäisen ja toisen asteen yhtälöiden ratkaisemista. Sana algoritmi johtuu al-khwārizmīn nimen latinankielisestä versiosta. Mainittakoon myös, että algebra on sanana peräisin edellä mainitun teoksen nimestä. Modernit matemaatikot pohtivat 1930-luvulta lähtien algoritmin käsitettä ja esittivät menetelmiä, jolla algoritmeja voitaisiin esittää ja analysoida käyttäen matemaattista formalismia. Meidän tarkoituksiimme riittää määritellä algoritmi äärelliseksi käskyjonoksi, joka suorittaa ennalta määritellyn tehtävän annetuilla syötteillä. Lisäksi oletetaan, että algoritmin askeleet ovat tarkasti määriteltyjä ja että algoritmi päättyy äärellisessä ajassa. Yleensä vaaditaan myös deterministisyyttä: algoritmi tuottaa samalla syötteellä aina saman tuloksen. Algoritmia voidaan myös tarkastella ratkaisuna laskennalliseen ongelmaan. Tyypillinen laskennallinen ongelma on järjestämisongelma (sorting problem): Syöte: Lukujono a1,a2, an, missä n on jokin positiivinen kokonaisluku. Tulostus: Syötteen luvut järjestyksessä a1, a2, an, niin että a1 a2 an. Ongelman ilmentymällä (instance) tarkoitetaan ongelmaa jollakin konkreettisella syötteellä. Esimerkiksi lukujonon 4,12,8,33,2,11,22 järjestäminen on järjestämisongelman eräs ilmentymä. Tällä kurssilla tutustutaan erilaisiin lajittelualgoritmeihin. On olemassa useita erilaisia lajittelualgoritmeja, joiden ominaisuudet poikkeavat monella tavalla toisistaan: lisäyslajittelu, lomituslajittelu, kekolajittelu, jne. Näiden soveltuvuus voi riippua monesta tekijästä, kuten esimerkiksi Kuinka suuri määrä lukuja järjestetään? Kuinka paljon muistia on käytössä? Ovatko luvut jo osittain järjestyksessä?
Lajittelu ei tietenkään ole ainoa laskennallinen ongelma, johon algoritmeja on suunniteltu. Algoritmeilla voidaan ratkaista mm. erilaisia päätösongelmia (decision problem) ja laskea matemaattisten funktioiden arvoja. Yleisimpiä algoritmien sovelluskohteita ovat ehkä kuitenkin erilaiset hakuongelmat (search problem) ja optimointiongelmat (optimization problem). Algoritmien suunnittelussa ja analysoinnissa käytetään erilaisia tekniikoita eli menettelytapoja erilaisten tehtävien suorittamiseen. Algoritmit ovat yksi ohjelmistojen suunnittelussa käytettävä teknologia; muita ohjelmistoihin suoraan liittyviä teknologioita ovat esimerkiksi graafiset käyttöliittymät ja oliopohjaiset järjestelmät. Tietokoneisiin ja niitä laajempiin järjestelmiin liittyvät myös monet muut teknologiat, kuten laitteistoteknologia (esim. prosessoriarkkitehtuurit). Algoritmit vaikuttavat ohjelman suorituskykyyn siinä missä esim. prosessorin ominaisuudet. Lisäksi algoritmit ovat monen teknologian taustalla: laitteiston suunnittelussa käytetään algoritmeja, oliopohjaisia ja muita ohjelmia käännettäessä ja tulkattaessa käytetään algoritmeja, tietoliikenteessä käytetään algoritmeja mm. reitityksessä ja signaalinkäsittelyssä, jne. Algoritmeista voidaan mitata useita ominaisuuksia, ja kahdella saman laskennallisen ongelman ratkaisevalla algoritmilla on usein monia eroja. Yleisin mitattu suure on algoritmin nopeus, eli aika, joka algoritmilla menee syötteen muuntamisessa tulosteeksi. Tällä kurssilla algoritmit esitetään (hieman vaihtelevalla tarkkuudella) määrittelemällä sen askeleet pseudokoodin avulla. Ohjelmointia hieman harrastaneen lukijan oletetaan ymmärtävän käytettävää pseudokoodia ilman eri määritelmiä. Huomattakoon, että kontrollirakenteiden vaikutusalue merkitään koodirivien sisennyksellä. Muuttujia ei erikseen tyypitetä eikä esitellä. Tällä kurssilla käytetään seuraavia merkintätapoja: 1. Koodirivit numeroidaan kasvavilla numeroilla. 2. Sijoituslauseessa käytetään merkkiä = kuten c-kielessä. Esimerkiksi x=y sijoittaa muuttujan y arvon muuttujan x arvoksi. 3. Yhtäsuuruusvertailussa käytetään (myös c-kielen tapaan) merkkiä ==. Esimerkiksi vertailu x==y on tosi, jos muuttujien x ja y arvot ovat samat. Alla on esimerkki algoritmin esitystavasta: Syöte: Taulukko A[1,..,n], n >= 1 Tulostus: Suurin luvuista A[1],, A[n] MAKSIMI(A) 1. i = 2 2. x = A[1] 3. while i <= n 4. if A[i] > x 5. x = A[i] 6. i = i+1 7. return x Edellä rivit 4-6 ovat while-silmukan sisällä ja ainoastaan rivi 5 kuuluu if-lauseeseen. Rivi 6 suoritetaan riippumatta siitä onko if-lauseen ehto tosi vai ei. Kuten helpohkosti voi havaita, algoritmi hakee syötetaulukon suurimman arvon.
Tällä kurssilla esiintyy myös monen tyyppisiä tietorakenteita. Tietorakenne tarkoittaa tapaa, jolla algoritmi säilöö ja järjestää käyttämäänsä dataa. Tehtävän luonne määrää, minkälainen tietorakenne on kulloinkin sopivin käytettäväksi. Näin ollen algoritmin suunnittelijan tulisi tuntea eri tietorakenteiden ominaisuudet ja rajoitukset. 1.2. Johdanto algoritmien analyysiin Algoritmeja analysoidaan kahdesta syystä: 1. varmistamaan algoritmin oikea toiminta, ts. algoritmin päättyminen ja asianmukainen tulos, 2. algoritmin suorituskyvyn tarkastelu. Ensimmäisen kohdan tärkeys lienee itsestään selvä, ohjelmoijan on kyettävä vakuuttamaan itselleen ja muille kirjoittamansa ohjelman oikea toiminta. Ohjelmointitekninen osaaminen ei vielä takaa laadukkaiden ohjelmien syntymistä, vaan ohjelma on suunniteltava huolellisesti ja sen kriittisiä kohtia on usein syytä tarkastella analyyttisesti. Jon Bentley on todennut: Coding skill is just one small part of writing correct programs. The majority of the task is problem definition, algorithm design, and data structure selection. ([Ben], Column 4: Writing Correct Programs). Algoritmin suorituskyky tarkoittaa sen kuluttamien resurssien määrää ja algoritmin kuluttamaa aikaa suhteessa syötteeseen. Tällä kurssilla perehdytään jälkimmäiseen, eli algoritmien aikakompleksisuuteen. Tietokoneet ovat nykyään erittäin nopeita ja nopeutuvat koko ajan. Onko syytä tutkia algoritmien suorituskykyä? Ohjelmoijan kannattaa paneutua asiaan useastakin syystä. Ensinnäkin, huonon algoritmin valinta voi monissa tapauksissa hidastaa ohjelman suoritusta merkittävästi ja jopa tehdä ohjelman käyttökelvottomaksi. Lisäksi on hyvä tuntea algoritmien suorituskyvyn rajoja: On olemassa ongelmia, joille ei sitkeästä yrittämisestä huolimatta ole keksitty nopeaa ratkaisua. Näin ollen tällaisessa tapauksessa on järkevää tyytyä hitaaseen ratkaisuun tai käyttää jotakin nopeaa likimääräistä ratkaisualgoritmia. Väärinkin toimivat algoritmit voivat olla hyödyllisiä, jos niiden virhemäärää voidaan hallita. Oletetaan, että tietokone suorittaa noin 10 000 000 perusoperaatiota sekunnissa ja että ollaan suorittamassa algoritmia, jonka suoritusaika (suoritettavien askelten lukumäärä) riippuu funktiosta f(n), kun n on syötteen koko. Seuraavasta taulukosta käy ilmi funktion vaikutus suoritusaikaan erikokoisilla syötteillä:
Syötteen koko f(n) 20 40 60 80 100 500 1000 n 2μs 4μs 6μs 8μs 10μs 50μs 0.1ms n lg(n) 8.6μs 21.3μs 35μs 50μs 66μs 0.45ms 1ms n 2 40 μs 0.16ms 0.36ms 0.64ms 1ms 0.025s 0.1s n 3 0.8ms 6.4ms 0.022s 0.05s 0.1s 1.25s 1min40s 2 n 0.1s 1.27 päivää n! 7700 2.6 10 33 3660 3.8 miljardia Huom! Tällä kurssilla käytetään, kuten teoksessa [Cor], merkintää lg(n) tarkoittamaan kaksikantaista logaritmia. Esimerkiksi lg(8) = lg(2 3 ) = 3. Yllä olevasta taulukosta käy ilmi, että ero suoritusajassa samankokoisella syötteellä voi olla varsin dramaattinen, jos kompleksisuus muuttuu polynomiaikaisesta (n 3 ) eksponentiaaliseksi (2 n ). Lisäksi huomataan, että prosessoinnin nopeutuminen ei välttämättä auta, jos algoritmi on hidas: esimerkiksi koneen muuttuminen 100 kertaa nopeammaksi muuttaisi syötteen koolla 60 taulukon eksponentiaalisen algoritmin suoritusajan 3600 vuodesta 36 vuoteen, mikä on aika laiha lohtu. Koska laskentateho ja muisti ovat usein rajattuja resursseja, tilanteeseen sopivan algoritmin valinta auttaa käyttämään niitä mahdollisimman järkevästi. On tärkeä ymmärtää, että järjestelmän suorituskyky ei riipu pelkästään nopeasta laitteistosta, vaan yhtälailla sopivista ja tehokkaista algoritmeista. Esimerkiksi sulautettua laitetta rakennettaessa algoritmit ovat tärkeässä roolissa; hyvä algoritmi voi esimerkiksi mahdollistaa halvemman prosessorin käyttämisen. Toinen esimerkki: 3G-verkon tukiasemat käsittelevät puhelimen lähettämän datan ja lähettävät sen eteenpäin verkkoon, jolloin vaatimuksena voi olla esim. kaikkien tukiasemaan kytkeytyneiden puhelinten datan lähettäminen eteenpäin 2 millisekunnin kuluessa datan vastaanotosta. Tämä on haastava vaatimus nykyisillä datansiirtonopeuksilla! Tehokkaat algoritmit voivat pienentää laitteistolle asetettavia vaatimuksia.
Seuraavaan listaan on valittu muutamia esimerkkejä, joissa kehittyneet algoritmit ovat merkittävässä osassa: Huhtikuussa 2003 valmistunut Human Genome Project eli HGP-hanke, jonka tavoitteiksi määriteltiin ihmisen kaikkien noin 20 000 25 000 geenin tunnistaminen ja koko miljardien emäsparien mittaisen DNA:n sekvensointi. Internetissä tarvitaan nopeita algoritmeja tiedon hakemiseen, keräämiseen ja lajitteluun. Esimerkiksi hakukoneiden on löydettävä pyydetty tieto nopeasti. Lisäksi esimerkiksi viestien reitittäminen vaatii omat algoritminsa. Sähköisessä kaupankäynnissä tiedon lisäksi siirretään ihmisten omaisuutta ja yksityistä tietoa. Tämän vuoksi erityisesti luottokorttitietojen, salasanojen ja pankkitietojen turvaamiseksi on suunniteltu kehittyneitä salausalgoritmeja. Taloudessa resurssien mahdollisimman tehokkaaseen käyttöön on kehitetty paljon optimointialgoritmeja. Algoritmi voi laskea esimerkiksi, miten tavarat saadaan kerättyä tai jaettua kuljetuskalustolla mahdollisimman tehokkaasti. Konenäkö voi tunnistaa videokuvasta esimerkiksi ihmiskasvoja. Videokuva on tietokoneelle numeroarvoja. Hahmojen (ihmisten, esineiden) tunnistaminen valtavasta määrästä numeerista dataa edellyttää kehittyneitä algoritmeja. Lähteet: [Ben] Bentley, J. Programming Pearls, Addidon Wesley Publishing Company 1986. [Cor] Cormen, T.H., Leiserson, C.E., Rivest, R.L., Stein, C. Introduction to Algorithms, 2 nd edition, The MIT Press 2001.