Tietorakenteet, laskuharjoitus 10, ratkaisuja 1. (a) Seuraava algoritmi tutkii, onko jokin luku taulukossa monta kertaa: SamaLuku(T ) 2 for i = 1 to T.length 1 3 if T [i] = = T [i + 1] 4 return True 5 return False Algoritmi järjestää ensin taulukon, minkä jälkeen mahdolliset toistuvat luvut ovat peräkkäin. Algoritmin aikavaativuus on O(n log n), koska taulukon järjestäminen on algoritmin työläin vaihe. (b) Seuraava algoritmi etsii taulukon useimmiten esiintyvän luvun: YleisinLuku(T ) 2 yluku = luku = T [1] 3 ymaara = maara = 1 4 for i = 2 to T.length 5 if T [i] = = luku 6 maara = maara + 1 7 else 8 luku = T [i] 9 maara = 1 10 if maara > ymaara 11 yluku = luku 12 ymaara = maara 13 return yluku Algoritmi järjestää ensin taulukon, minkä jälkeen luvut ovat ryhmissä niiden suuruuden mukaan. Algoritmin aikavaativuus on O(n log n), koska taulukon järjestäminen on algoritmin työläin vaihe. (c) Seuraava algoritmi etsii taulukosta luvut, joiden ero on pienin: PieninEro(T ) 2 pienin = T [2] T [1] 3 for i = 2 to T.length 1 4 if T [i + 1] T [i] < pienin 5 pienin = T [i + 1] T [i] 6 return pienin 1
Algoritmi järjestää taulukon, minkä jälkeen luvut, joiden ero on pienin, ovat peräkkäin. Algoritmin aikavaativuus on O(n log n), koska taulukon järjestäminen on algoritmin työläin vaihe. 2. Seuraava algoritmi tutkii, onko kahden taulukon luvun summa k: KahdenSumma(T, k) 2 alku = 1 3 loppu = T.length 4 while alku < loppu 5 if T [alku] + T [loppu] = = k 6 return True 7 if T [alku] + T [loppu] < k 8 alku = alku + 1 9 else 10 loppu = loppu 1 11 return False Algoritmi järjestää ensin taulukon ja yrittää sitten muodostaa summaa k. Algoritmi käy taulukon lukujen pareja läpi alusta ja lopusta kahden muuttujan avulla. Joka vaiheessa jos lukujen summa on liian pieni, algoritmi siirtää ensimmäistä kohtaa alku oikealle, ja jos lukujen summa on liian suuri, algoritmi siirtää toista kohtaa loppu vasemmalle. Algoritmin aikavaativuus on O(n log n), koska taulukon järjestäminen on algoritmin työläin vaihe. Seuraava algoritmi tutkii, onko kolmen taulukon luvun summa k: KolmenSumma(T, k) 2 for pohja = 1 to T.length 2 3 alku = pohja + 1 4 loppu = T.length 5 while alku < loppu 6 if T [pohja] + T [alku] + T [loppu] = = k 7 return True 8 if T [pohja] + T [alku] + T [loppu] < k 9 alku = alku + 1 10 else 11 loppu = loppu 1 12 return False Algoritmi valitsee muuttujaan pohja ensimmäisen summaan kuuluvan luvun. Tämän jälkeen algoritmin toiminta vastaa kahden luvun summan hakua jäl- 2
jellä olevasta taulukon osasta. Nyt algoritmin työläin vaihe on sen loppuosa: muuttuja pohja saa O(n) eri arvoa ja kahden muun luvun etsiminen vie aikaa O(n), joten aikaa kuluu yhteensä O(n 2 ). Seuraava algoritmi tutkii, onko neljän taulukon luvun summa k: NeljanSumma(T, k) 2 for i = 1 to T.length 3 for j = i + 1 to T.length 4 lisää kolmikko (T [i] + T [j], i, j) taulukkoon T 5 järjestä taulukko T / kolmikoiden ensimmäisen eroavan luvun mukaan 6 alku = 1 7 loppu = T.length 8 while alku < loppu 9 if T [alku][1] + T [loppu][1] = = k 10 if T [alku][3] < T [loppu][2] 11 return True 12 else 13 alku = alku + 1 14 loppu = loppu 1 15 if T [alku][1] + T [loppu][1] < k 16 alku = alku + 1 17 else 18 loppu = loppu 1 19 return False Algoritmi muodostaa taulukon T, joka sisältää kaikki taulukon T erilliset lukuparit sekä niitä vastaavien lukujen indeksit. Tämän jälkeen algoritmi etsii lukuparien pareja, joiden summa on k. Algoritmi hyväksyy lukuparien parin vain, jos ensimmäisen parin toisen luvun indeksi tulee ennen toisen parin ensimmäisen luvun indeksiä. Taulukko T sisältää O(n 2 ) alkiota, joten sen järjestäminen vie aikaa O(n 2 log(n 2 )) eli O(n 2 log n). Seuraavassa esimerkissä taulukossa T on 5 lukua ja taulukossa T on 10 lukuparia. Jos k = 16, algoritmi löytää taulukon T luvut 2, 4, 4 ja 6, koska taulukossa T ovat lukuparit 6,1,3 ja 10,4,5 (summa ja T :n indeksit). T 2 3 4 4 6 T 5,1,2 6,1,3 6,1,4 7,2,3 7,2,4 8,1,5 8,3,4 9,2,5 10,3,5 10,4,5 3
3. Edellisviikon algoritmien lisäksi toteutettiin pika- ja lomitusjärjestäminen sekä käärittiin Javan Arrays.sort mittauskehykselle sopivaan muotoon. Ohjelmakoodit ovat omissa Java-tiedostoissaan. Ohessa on mittaustulokset järjestettäessä ensin satunnaisessa ja sitten käänteisessä järjestyksessä oleva taulukko. n heap b_radix s_radix quick merge arrays insert ---------------------------------------------------------------- 10000 21 14 16 19 14 31 30 20000 6 2 10 1 5 7 103 40000 15 3 1 11 13 15 382 80000 13 7 7 18 10 19 1462 160000 28 13 10 21 34 28 5935 320000 60 16 15 38 48 45-1 640000 121 30 24 78 83 89-1 1280000 255 45 49 145 170 176-1 2560000 588 116 96 339 364 379-1 5120000 1459 169 174 620 746 752-1 n heap b_radix s_radix quick merge arrays insert ---------------------------------------------------------------- 10000 1 0 1 1 1 0 55 20000 5 2 1 2 2 2 194 40000 11 4 3 3 6 3 735 80000 11 8 4 5 12 8 2970 160000 28 14 5 12 8 16 11782 320000 42 21 15 24 24 18-1 640000 88 36 27 38 53 31-1 1280000 178 66 55 46 89 54-1 2560000 386 133 134 83 175 106-1 5120000 781 310 289 163 383 223-1 Itse toteutettu pikajärjestäminen optiomoitiin valitsemalla jakoalkioksi aina mediaani kolmesta luvusta. Tämä johtaa lähes optimaaliseen suoritukseen myös käänteisessä järjestyksessä olevilla taulukoilla. Javan oma taulukkojen järjestämiseen tarkoitettu Arrays.sort osoittautui myös melko nopeaksi. Tuloksissa esiintyvät kantalukujärjestäminen (engl. Radix sort) pärjäsivät hyvin ja osoittautuivat ylivoimaisiksi satunnaisen järjestyksen tapauksessa. 4. Seuraava algoritmi sekoittaa taulukon: Sekoitus(T ) 1 for i = T.length downto 2 2 s = Random(1, i) 3 vaihda T [s] ja T [i] 4
Algoritmi käy taulukon läpi lopusta alkuun ja vaihtaa joka vaiheessa keskenään satunnaisen alkion taulukon alkuosasta sekä käsiteltävän alkion. Seuraavassa on esimerkki algoritmin suorituksesta, kun sekoitettava taulukko sisältää aluksi luvut 1... 5 järjestyksessä: lähtötilanne arvonta lopputulos 1. 1 2 3 4 5 Random(1, 5) = 2 1 5 3 4 2 2. 1 5 3 4 2 Random(1, 4) = 3 1 5 4 3 2 3. 1 5 4 3 2 Random(1, 3) = 3 1 5 4 3 2 4. 1 5 4 3 2 Random(1, 2) = 1 5 1 4 3 2 Algoritmi käy taulukon kerran läpi, joten sen aikavaativuus on O(n), ja vaatii vain vakiomäärän lisätilaa, joten sen tilavaativuus on O(1). Kaikki järjestykset ovat yhtä todennäköisiä, koska aluksi jokaisella alkiolla on yhtä suuri todennäköisyys päätyä taulukon viimeiseksi alkioksi, minkä jälkeen jäljelle jää yhtä alkiota pienempi taulukko, johon soveltuu sama päättely. Jos tarkasteltava alkio vaihdettaisiin aina minkä tahansa alkion kanssa, niin alussa käsiteltävillä alkioilla olisi muita alkioita suurempi todennäköisyys tulla käsitellyksi useaan kertaan. 5. Seuraava algoritmi muodostaa verkon transpoosin: Transpoosi(G) 1 for jokaiselle solmulle u V 2 for jokaiselle solmulle v Adj[u] 3 lisää u listaan Adj T [v] 4 return Ajd T Algoritmi käy läpi kaikkien solmujen vieruslistat ja muodostaa uudet vieruslistat käänteisistä kaarista. Algoritmin aikavaativuus on O( V + E ), koska se käy läpi kerran kaikki solmut ja kaikki kaaret. 5