170 likes | 395 Views
Algoritmit ja laskennan vaativuus. Tietotekniikan perusteet 10.11.1998 Pekka Orponen. Algoritmit ja ohjelmat. Algoritmi: täsmällinen kuvaus jonkin (laskenta)tehtävän suoritustavasta. Ohjelma: Algoritmin esitys jollakin tietokoneella suoritettavissa olevalla ohjelmointikielellä.
E N D
Algoritmit ja laskennan vaativuus Tietotekniikan perusteet 10.11.1998 Pekka Orponen
Algoritmit ja ohjelmat • Algoritmi: täsmällinen kuvaus jonkin (laskenta)tehtävän suoritustavasta. • Ohjelma: Algoritmin esitys jollakin tietokoneella suoritettavissa olevalla ohjelmointikielellä. • Esim. Valintalajittelualgoritmi lukujoukon järjestämiseksi suuruusjärjestykseen: Olkoot lajiteltavat luvut talletettu taulukon A alkioiksi A[1] ... A[n]. 1. Aseta i ¬ 1. 2. Etsi taulukon alkioista A[i] ... A[n] pienin. Olkoon tämän indeksi imin. 3. Vaihda alkiot A[i] ja A[imin] keskenään. 4. Aseta i ¬ i+1; jos i < n, palaa riville 2. 5. Kun i = n, taulukon alkiot ovat suuruusjärjestyksessä. • Algoritmin esitys C-ohjelmointikielellä: int A[nmax+1]; void selectsort(int n) { int i, j, amin, imin, atmp; for (i = 1; i < n; i++) { amin = A[i]; imin = i; for (j = i+1; j <= n; j++) if (A[j] < amin) { amin = A[j]; imin = j;} A[imin] = A[i]; A[i] = amin; } }
Valintalajitteluohjelma #include <stdio.h> #include <stdlib.h> #include <time.h> #define nmax 1000000 int A[nmax+1]; void selectsort(int n) {...} main() { int n, i; float t0, t1; printf("Number of items = "); while (scanf("%i", &n)) { if (n > nmax) {printf("Number too large. Try again: "); continue;} printf("Generating %d random numbers in the range 0..%d.\n", n, RAND_MAX); for (i = 1; i <= n; i++) A[i] = rand(); printf("Beginning selection sort of %d items\n", n); t0 = ((float) clock())/((float) CLOCKS_PER_SEC); selectsort(n); t1 = ((float) clock())/((float) CLOCKS_PER_SEC); printf("Done. Time = %.2f seconds.\n", t1-t0); printf("Number of items = "); } }
Valintalajitteluohjelman suoritusaika • Käännetään ja suoritetaan edellinen ohjelma: tukki% cc -o selectsort.out selectsort.c tukki% selectsort.out Number of items = 1000 Generating 1000 random numbers in the range 0..32767. Beginning selection sort of 1000 items Done. Time = 0.04 seconds. Number of items = 2000 Generating 2000 random numbers in the range 0..32767. Beginning selection sort of 2000 items Done. Time = 0.16 seconds. Number of items = 3000 Generating 3000 random numbers in the range 0..32767. Beginning selection sort of 3000 items Done. Time = 0.36 seconds. … Number of items = 10000 Generating 10000 random numbers in the range 0..32767. Beginning selection sort of 10000 items Done. Time = 4.14 seconds.
Valintalajitteluohjelman suoritusaika (jatkuu) • Ohjelman suoritusaika-arvot kaaviona (x-aks. lukujen määrä/1000, y-aks. aika sekunteina): • Ohjelman suoritusaika näyttää kasvavan kuten lajiteltavien lukujen määrän neliö. Sanotaan, että suoritusaika on kertaluokkaa n2, tai lyhyesti O(n2) (engl. ”order of”), missä n on lajiteltavien lukujen määrä.
Valintalajittelualgoritmin analyysi • Valintalajitteluohjelman neliöllinen suoritusaika olisi voitu ennustaa myös suoraan käytettyä algoritmia analysoimalla. • Algoritmi aiempaa täsmällisemmässä ”pseudokoodimuodossa”: 1. toista i:n arvoilla 1..n-1: 2. imin ¬ i 3. toista j:n arvoilla i+1 .. n: 4. jos A[j] < A[imin], niin imin ¬ j 5. vaihda alkiot A[i] ja A[imin] keskenään. • Jos oletetaan, että kunkin ”alkeisoperaation” suoritusaika on 1 aikayksikkö, niin kullakin i:n arvolla (i = 1 .. n-1) on: • rivin 2 suoritusaika 1 yksikkö • rivien 3-4 suoritusaika n-i yksikköä • rivin 5 suoritusaika 1 yksikkö • Kaikkiaan algoritmin suoritusaika n alkion syötetaulukolla saadaan tästä summaamalla yli kaikkien i:n arvojen: time(n) = åin= 1 ((n-i)+2) = 1/2n2 + 3/2n - 2 = O(n2).
Lomituslajittelualgoritmi • Toinen menetelmä lukujoukon järjestämiseen on seuraava ”rekursiivinen” (osittava) algoritmi: Olkoot lajiteltavat luvut talletettu taulukon A alkioiksi A[1] ... A[n]. Oletetaan yksinkertaisuuden vuoksi että luku n on muotoa 2k jollakin k = 0,1,2,... 1. Jos n = 1, taulukossa A on vain yksi alkio, eikä mitään tarvitse tehdä. 2. Jos n ³ 1, jaa taulukko A kahteen yhtäsuureen puolikastalukkoon, A’ = A[1..n/2] ja A’’ = A[n/2+1..n] ja käytä tässä kuvattua menetelmää ensin kummankin puolikkaan lajitteluun. 3. Kun puolikastaulukot A’ ja A’’ on lajiteltu, lomita niiden alkiot yhteen taulukkoon A poimimalla alkioita kummastakin vuorollaan suuruus- järjestyksessä. • Esimerkiksi taulukon A = [3 1 4 2] lajittelu tällä menetelmällä sujuu seuraavasti: [3 1 4 2] ositus [3 1] [4 2] ositus [3] [1] [4] [2] lomitus [1 3] [2 4] lomitus [1 2 3 4]
Lomituslajittelu C-ohjelmana • Algoritmin toteutus C-ohjelmana (valintalajittelu- ohjelman kanssa yhteiset osat poistettu): int A[nmax+1], B[nmax+1]; void merge(int m, int h, int n) /* Lomittaa taulukon A järjestetyt osataulukot */ /* A[m..h], A[h+1..n] yhteen taulukoksi A[m..n]. */ { int i, j, k, l; i = m; j = h+1; k = 0; while ((i <= h) && (j <= n)) if (A[i] <= A[j]) { B[k] = A[i]; i++; k++; } else { B[k] = A[j]; j++; k++; }; while (i <= h) { B[k] = A[i]; i++; k++; }; while (j <= n) { B[k] = A[j]; j++; k++; }; k--; for (l = 0; l <= k; l++) A[m+l] = B[l]; } void mergesort(int m, int n) { int h; if (m == n) return; h = (m+n)/2; mergesort(m,h); mergesort(h+1,n); merge(m,h,n); } main() { int n,i; ... mergesort(1,n); ...}
Lomituslajitteluohjelman suoritusaika • Lomituslajittelu on huomattavasti tehokkaampi algoritmi kuin valintalajittelu. Seuraavassa C-toteutuksen suoritusaikakaavio (x-aks. lukujen määrä/1000, y-aks. aika sekunteina; huom. eri mittakaava kuin aiemmin): • Ohjelman suoritusaika näyttää nyt kasvavan lähes lineaarisesti lajiteltavien lukujen määrän suhteen. Onko näin?
Lomituslajittelualgoritmin analyysi • Merkitään lomituslajittelualgoritmin n alkion taulukon A[1..n] lajitteluun tarvitsemaa aikaa T(n):llä. Oletetaan yksinkertaisuuden vuoksi, että n on jokin kakkosen potenssi ja että kunkin ”alkeisoperaation” kesto on 1 aikayksikkö. • Helposti nähdään, että taulukon A[1..n] puolittamiseen ja lajiteltujen puolikastaulukoiden yhdistämiseen kuluva aika on O(n) yksikköä. Ajatellaan yksinkertaisuuden vuoksi, että tämä aika on tasan n yksikköä. • Koko algoritmin suoritusaikaa kuvaa tällöin rekursioyhtälö T(1) = 1, T(n) = 2T(n/2) + n, kun n = 2k, k ³ 1. • Voidaan osoittaa (tai tarkastaa esim. induktiolla), että tämän rekursioyhtälön ratkaisu on T(n) = n log2 n + n. • Lomituslajittelun aikavaativuus ei siis ole aivan lineaarinen, vaan kertaluokkaa O(n log2 n). Kuten nähtiin, tämä on kuitenkin huomattavasti parempi saavutus kuin valintalajittelun O(n2).
Lyhimpien reittien laskeminen • Toisentyyppisenä esimerkkinä tarkastellaan seuraavaa laskentatehtävää: on annettu etäisyystaulukko D[1..n,1..n], joka kuvaa n kaupungin pareittaisia etäisyyksiä. Taulukkoon on kuitenkin talletettu tiedot vain suorista tieyhteyk-sistä kaupunkien välillä; etäisempien kaupunki-parien kohdalla taulukossa on tyhjää. Tehtävänä on täydentää taulukon puuttuvat etäisyydet kaupunkien välisten lyhimpien reittien mukaan.
Lyhimpien reittien laskeminen (jatkuu) • Esimerkiksi syötetaulukko voisi sisältää seuraavat eräiden Suomen kaupunkien etäisyystiedot: Jkl Vaa Jsu Tre Hln Lti Lrt Tku Hki Jkl 0 283 244 151 170 220 Vaa 238 0 244 332 Jsu 244 0 236 Tre 151 244 0 75 153 Hln 75 0 80 139 98 Lti 170 80 0 113 110 Lrt 220 236 113 0 Tku 332 153 139 0 165 Hki 98 110 165 0 • Taulukko pitäisi tällöin täydentää muotoon: Jkl Vaa Jsu Tre Hln Lti Lrt Tku Hki Jkl0 283 244 151 226 170 220 304 280 Vaa 238 0 482 244 319 399 458 332 417 Jsu 244 527 0 395 429 349 236 548 459 Tre 151 244 395 0 75 155 268 153 173 Hln 226 319 429 75 0 80 193 139 98 Lti 170 399 349 155 80 0 113 219 110 Lrt 220 503 236 268 193 113 0 332 223 Tku 304 332 548 153 139 219 332 0 165 Hki 280 417 459 173 98 110 223 165 0
Floydin algoritmi • Tehtävä ratkeaa tyylikkäästi seuraavalla ns. Floydin algoritmilla: • talletetaan taulukon D tyhjiin paikkoihin jokin hyvin suuri luku dmax (”ääretön”, esim. kaikkien D:n epätyhjien alkioiden summa) • suoritetaan seuraavat kolme sisäkkäistä silmukkaa: toista k:n arvoilla 1..n: toista i:n arvoilla 1..n: toista j:n arvoilla 1..n: jos D[i,k] + D[k,j] < D[i,j], niin aseta D[i,j] ¬ D[i,k] + D[k,j] • Algoritmin idea (lyhyesti kuvattuna) on, että kullakin silmukkamuuttujan k arvolla määritetään lyhimmät sellaiset kaupunkiparien i, j väliset reitit, jotka kulkevat enintään kaupunkien 1..k kautta. • Algoritmin aikavaativuus on selvästi luokkaa O(n3): kolme sisäkkäistä silmukkaa, kukin silmukka-muuttujan arvoilla 1..n.
Kauppamatkustajan ongelma • Annettu n kaupungin (täydellinen) etäisyystaulukko D[1..n, 1..n]. Tehtävänä on löytää lyhin sellainen kaupunkikierros, joka kulkee täsmälleen kerran kunkin kaupungin kautta -- siis sellainen lukujen 1,…,n permutaatio p(1),…,p(n), joka minimoi kustannuksen c(p) = å1n-1D[p(i), p(i+1)] + D[p(n), p(1)]. • Tämä ”kauppamatkustajan ongelma” (engl. TSP = ”traveling salesman problem”) esiintyy jossain muodossa lukuisissa käytännön sovelluksissa: jakeluautojen reittisuunnittelussa, piirilevyjen porauksessa, rinnakkaistietokoneiden töiden-järjestelyssä, DNA-jonojen sekvenoinnissa jne. • Vaikka ongelma päällisin puolin hieman muistuttaa lyhimpien reittien määritysongelmaa, se on huomattavasti vaikeampi.
TSP-ongelman ratkaisuyritelmiä • Triviaaliratkaisu: Kokeillaan n kaupungin kartalla kaikki n! mahdollista reittiä ja valitaan niistä lyhin. • Ei onnistu käytännössä: jos esim. n = 22, niin n! on noin 1021. Jos pystyttäisiin tutkimaan esim. 1 reitti millisekunnissa, vaatisi kaikkien reittien tutkiminen noin 36 miljardia vuotta. (Nykykäsityksen mukaan on maailmankaikkeuden tähänastinen ikä 10-20 miljardia vuotta.) • Parannusidea: Ostetaan nopeampi tietokone: esimerkiksi miljoonan prosessorin rinnakkaiskone, jonka kukin prosessori tutkii yhden reitin nano-sekunnissa. • Ei auta: 20 mrd vuotta riittää silti vasta 30 kaupungin karttoihin. • Parempi idea: keksitään tehokkaampi algoritmi. • rajoitusheuristiikat, ”simuloitu jäähdytys”, ”geneettiset algoritmit” • auttavat muutamien kymmenien (joskus satojen) kaupunkien tapauksiin asti, mutta eivät pidemmälle
Polynominen ja eksponentiaalinen aika • Perusvaikeus TSP-ongelmassa (ja muissa samantapaisissa tehtävissä) on tutkittavien ratkaisuvaihtoehtojen (reittien) määrän n! » nn eksponentiaalinen kasvu (”kombinatorinen räjähdys”): • jos ratkaisuvaihtoehtojen määrä kasvaa vain polynomisesti, m £ nk jollakin k, konetehonkin kasvattaminen auttaa: 10 kertaa aiempaa tehok-kaammalla koneella pystytään ratkomaan 101/k kertaa aiempaa suurempia ongelman tapauksia • jos ratkaisuvaihtoehtojen määrä kasvaa eksponen-tiaalisesti, m ³ cn jollakin c > 1, konetehon lisää-minen ei auta: tehon 10-kertaistaminen kasvattaa ratkaistavien tapausten kokoa enintään vakiolla logc 10. • ”Käytännössä ratkeavina” pidetään ongelmia, joilla on jokin syötteen koon n suhteen polynomisessa ajassa toimiva ratkaisumenetelmä, esim.: • lukujoukon järjestäminen (menetelmiä: valintalajittelu O(n2), lomituslajittelu O(n log2 n)) • lyhimpien reittien laskeminen (menetelmä: Floydin algoritmi O(n3)). • Ongelmaluokka P = ”polynomisessa ajassa ratkeavat ongelmat”.
”P = NP” -ongelma • Kauppamatkustajan ongelmalle ei tunneta yhtään kaupunkien määrän n suhteen polynomisessa ajassa toimivaa ratkaisumenetelmää. • Tällaisen menetelmän olemassaoloa ei kuitenkaan ole myöskään osattu todistaa mahdottomaksi. Siten on mahdollista, joskin hyvin epäuskottavaa, että olisi TSP Î P. • TSP-ongelma kuuluu tiettyyn suureen ”vaikean tuntuisten” ongelmien ekvivalenssiluokkaan, ns. NP-täydellisiin ongelmiin, joilla on se ominaisuus että ne ovat joko kaikki helppoja (luokassa P) tai kaikki vaikeita (luokan P ulkopuolella). • Tällä hetkellä ei tiedetä, kumpi vaihtoehto on tosi. Tämä on kuuluisa ratkaisematon kysymys, ns. ”P = NP” -ongelma.