CS-A1140 Tietorakenteet ja algoritmit

Samankaltaiset tiedostot
Kierros 2: Järjestämisalgoritmeja

Algoritmit 1. Luento 11 Ti Timo Männikkö

Algoritmit 1. Luento 12 Ti Timo Männikkö

Tietorakenteet, laskuharjoitus 10, ratkaisuja. 1. (a) Seuraava algoritmi tutkii, onko jokin luku taulukossa monta kertaa:

Algoritmit 1. Luento 12 Ke Timo Männikkö

4 Tehokkuus ja algoritmien suunnittelu

TIE Tietorakenteet ja algoritmit 25

9 Erilaisia tapoja järjestää

58131 Tietorakenteet ja algoritmit (kevät 2016) Ensimmäinen välikoe, malliratkaisut

On annettu jono lukuja tai muita alkioita, joiden välille on määritelty suuruusjärjestys. Tehtävänä on saattaa alkiot suuruusjärjestykseen.

A TIETORAKENTEET JA ALGORITMIT

5 Kertaluokkamerkinnät

Mukautuvat järjestämisalgoritmit

58131 Tietorakenteet (kevät 2009) Harjoitus 9, ratkaisuja (Antti Laaksonen)

Tietorakenteet ja algoritmit. Järjestäminen. Ari Korhonen

1 Erilaisia tapoja järjestää

Algoritmit 2. Luento 8 To Timo Männikkö

Algoritmit 1. Luento 10 Ke Timo Männikkö

Algoritmit 2. Luento 13 Ti Timo Männikkö

Algoritmit 1. Luento 3 Ti Timo Männikkö

Algoritmit 2. Luento 3 Ti Timo Männikkö

Tietorakenteet ja algoritmit - syksy

Olkoon S(n) kutsun merge-sort(a, p, q) tilavaativuus kun p q + 1 = n. Oletetaan merge toteutetuksi vakiotyötilassa (ei-triviaalia mutta mahdollista).

Algoritmit 1. Luento 5 Ti Timo Männikkö

Algoritmit 2. Luento 3 Ti Timo Männikkö

Algoritmit 2. Luento 14 Ke Timo Männikkö

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

811312A Tietorakenteet ja algoritmit, , Harjoitus 7, ratkaisu

Pikalajittelu: valitaan ns. pivot-alkio esim. pivot = oikeanpuoleisin

1. (a) Seuraava algoritmi tutkii, onko jokin luku taulukossa monta kertaa:

811312A Tietorakenteet ja algoritmit III Lajittelualgoritmeista

8. Lajittelu, joukot ja valinta

58131 Tietorakenteet ja algoritmit (syksy 2015)

Algoritmit 2. Luento 7 Ti Timo Männikkö

Algoritmit 2. Luento 2 To Timo Männikkö

811312A Tietorakenteet ja algoritmit, VI Algoritmien suunnitteluparadigmoja

A TIETORAKENTEET JA ALGORITMIT

Algoritmit 2. Demot Timo Männikkö

Algoritmit 2. Luento 2 Ke Timo Männikkö

TAMPEREEN TEKNILLINEN YLIOPISTO

TAMPEREEN TEKNILLINEN YLIOPISTO

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

Tietorakenteet ja algoritmit syksy Laskuharjoitus 1

Tämä on helpompi ymmärtää, kun tulkitaan keko täydellisesti tasapainotetuksi binääripuuksi, jonka juuri on talletettu taulukon paikkaan

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

Ohjelmoinnin perusteet Y Python

Algoritmit 1. Luento 10 Ke Timo Männikkö

Kierros 4: Binäärihakupuut

Algoritmit 2. Luento 12 To Timo Männikkö

Tietorakenteet ja algoritmit

Algoritmit 1. Luento 2 Ke Timo Männikkö

Hakupuut. tässä luvussa tarkastelemme puita tiedon tallennusrakenteina

A TIETORAKENTEET JA ALGORITMIT KORVAAVAT HARJOITUSTEHTÄVÄT 3, DEADLINE KLO 12:00

811312A Tietorakenteet ja algoritmit , Harjoitus 2 ratkaisu

CS-A1140 Tietorakenteet ja algoritmit

useampi ns. avain (tai vertailuavain) esim. opiskelijaa kuvaavassa alkiossa vaikkapa opintopistemäärä tai opiskelijanumero

2. Seuraavassa kuvassa on verkon solmujen topologinen järjestys: x t v q z u s y w r. Kuva 1: Tehtävän 2 solmut järjestettynä topologisesti.

Tietorakenteet, laskuharjoitus 7, ratkaisuja

Algoritmit 2. Luento 13 Ti Timo Männikkö

Algoritmit 2. Luento 9 Ti Timo Männikkö

Datatähti 2019 loppu

f(n) = Ω(g(n)) jos ja vain jos g(n) = O(f(n))

lähtokohta: kahden O(h) korkuisen keon yhdistäminen uudella juurella vie O(h) operaatiota vrt. RemoveMinElem() keossa

Tiraka, yhteenveto tenttiinlukua varten

Algoritmit 1. Demot Timo Männikkö

Algoritmien suunnittelu ja analyysi (kevät 2004) 1. välikoe, ratkaisuja

ja λ 2 = 2x 1r 0 x 2 + 2x 1r 0 x 2

811312A Tietorakenteet ja algoritmit, , Harjoitus 3, Ratkaisu

Tietorakenteet ja algoritmit

Tietorakenteet, laskuharjoitus 3, ratkaisuja

(p j b (i, j) + p i b (j, i)) (p j b (i, j) + p i (1 b (i, j)) p i. tähän. Palaamme sanakirjaongelmaan vielä tasoitetun analyysin yhteydessä.

1.4 Funktioiden kertaluokat

Algoritmit 1. Luento 7 Ti Timo Männikkö

Algoritmit 1. Demot Timo Männikkö

Kierros 1: Algoritmianalyysin ja tietorakenteiden perusteita

58131 Tietorakenteet ja algoritmit (kevät 2014) Uusinta- ja erilliskoe, , vastauksia

58131 Tietorakenteet Erilliskoe , ratkaisuja (Jyrki Kivinen)

Kääreluokat (oppikirjan luku 9.4) (Wrapper-classes)

Algoritmit 1. Demot Timo Männikkö

ITKP102 Ohjelmointi 1 (6 op)

ALGORITMIT 1 DEMOVASTAUKSET KEVÄT 2012

Kierros 6: Dynaaminen ohjelmointi ja ahneet algoritmit

Ohjelmoinnin peruskurssi Y1

A ja B pelaavat sarjan pelejä. Sarjan voittaja on se, joka ensin voittaa n peliä.

Luku 8. Aluekyselyt. 8.1 Summataulukko

Algoritmit 1. Luento 14 Ke Timo Männikkö

Rakenteiset tietotyypit Moniulotteiset taulukot

TKT20001 Tietorakenteet ja algoritmit Erilliskoe , malliratkaisut (Jyrki Kivinen)

A TIETORAKENTEET JA ALGORITMIT

8. Lajittelu, joukot ja valinta

Tietorakenteet ja algoritmit

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

Ohjelmoinnin perusteet Y Python

58131 Tietorakenteet ja algoritmit (kevät 2013) Kurssikoe 2, , vastauksia

Informaatioteknologian laitos Olio-ohjelmoinnin perusteet / Salo

Harjoitustyö: virtuaalikone

Ohjelmoinnin perusteet Y Python

AVL-puut. eräs tapa tasapainottaa binäärihakupuu siten, että korkeus on O(log n) kun puussa on n avainta

Olio-ohjelmointi Syntaksikokoelma

Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan.

Transkriptio:

CS-A1140 Tietorakenteet ja algoritmit Kierros 2: Järjestämisalgoritmeja Tommi Junttila Aalto-yliopisto Perustieteiden korkeakoulu Tietotekniikan laitos Syksy 2016

Materiaali Kirjassa Introduction to Algorithms, 3rd ed. (online via Aalto lib): Lisäysjärjestäminen: kappaleet 2.1 ja 2.2 Lomitusjärjestäminen: kappale 2.3 Pikajärjestäminen: luku 7 Asymptoottisia alarajoja: kappale 8.1 Laskemis- ja kantalukujärjestäminen: kappaleet 8.2 8.4 Huom! Tällä kierroksella esitetään muutama yleisin järjestämisalgoritmi. Näiden lisäksi on olemassa useita muitakin; esimerkiksi tälle wikipediasivulle on koottu eri algoritmeja ja niiden välinen vertailu ajoajan, vaaditun työmuistin jne suhteen. 2/80

Osittain vastaavia tekstejä muualla: Luku 2 kirjassa Algorithms, 4th ed. OpenDSA Joitain netistä löytyviä videoluentoja ja visualisoiteja: MIT 6.006 OCW video on insertion and merge sorts MIT 6.006 OCW video on counting sort, radix sort and lower bounds for sorting and searching MIT OCW video on Quicksort, Randomized Algorithms MIT 6.046J 2015 OCW video on Randomized Select and Randomized Quicksort 15 Sorting Algorithms in 6 Minutes video www.sorting-algorithms.com visualizations of comparison based sorting algorithms http://aarondufour.com/tools/visualizer/ 3/80

Motivointi Datan järjestäminen on hyvin yleinen tehtävä: opintosuoritusten järjestäminen opiskelijanumeron perusteella, serverin lokitietojen läpikäynti aikajärjestyksessä,... Järjestämistä käytetään myös alirutiinina monessa muussa algoritmissa puolitushaku duplikaattialkioiden poistaminen datasta verkkojen läpikäyntialgoritmeissa... Järjestämiseen on onneksi hyvin tehokkaita algoritmeja Järjestäminen on melkein yhtä nopeaa kuin pelkkä datan sisäänlukeminen 4/80

Asetelma Tarkastellaan sekvenssejä (yleensä taulukko) alkioita Alkioiden välillä on jokin järjestysrelaatio Jos alkiot ovat rakenteellisia olioita, sekvenssi sisältää Scalassa/Javassa viittauksia alkioihin. Tällöin kahden alkion paikan vaihtaminen (engl. swap) on tehokasta olion koosta riippumatta Array[String] 0 1 2 3 Array[String] 0 1 2 3 String String String String CS-A1110 CS-A1120 CS-A1140 CS-A1141 String String String String CS-A1110 CS-A1120 CS-A1140 CS-A1141 Rakenteellisten olioiden vertailu voi olla ei-vakioaikaista Tehokkuusmittareita järjestämisalgoritmeille: kokonaisajoaika, taulukon luku/kirjoitusoperaatioiden määrä, vaihtojen määrä, vertailujen määrä 5/80

Rakenteellisia olioita voidaan järjestää myös tietyn kentän/alirakenteen eli avaimen (engl. key) mukaan Esimerkiksi alla työntekijät on järjestetty iän mukaan huomaa, että kaksi erillistä oliota voivat olla avaimen osalta yhtä suuria Array[Employee] 0 1 2 3 Employee Employee Employee Employee name age salary name age salary name age salary name age salary 18 4000 24 2200 24 2700 32 1900 String String String String St. Artup J. Smith P. Space B. Tree Tarkastellaan jatkossa selkeyden vuoksi yleensä vain kokonaislukutaulukkoja yleistäminen rakenteellisille olioille ja avaimille melko suoraviivaista 6/80

Paikallaan järjestäminen Määritelmä Järjestämisalgoritmi toimii paikallaan (engl. in place) jos sen ajon aikana kunakin ajanhetkenä vain jokin vakiomäärä alkioita on talletettu järjestettävän taulukon ulkopuolelle. Täten paikallaan järjestävät algoritmit eivät tarvitse suurta määrää ylimääräistä muistia tämä voi olla tärkeää kun järjestetään suuria datamääriä tai kun käytössä on vain suhteellisen pieni määrä muistia (sulautetut järjestelmät jne) Monet järjestämisalgoritmit, jotka eivät toimi paikallaan, käyttävät tyypillisesti aputaulukkoa, jonka koko on Θ(n), missä n on järjestettävän taulukon koko 7/80

Yllä oleva määritelmä on suhteellisen salliva eli se sallii esimerkiksi lineaarisen määrän indeksejä käytettävän rekursiivisessa kutsupinossa myös tiukempia versioita määrittelystä löytyy, ks. esimerkiksi tältä wikipedia-sivulta Yleisemmin voidaan puhua algoritmin vaatimasta työmuistin (tai lisämuistin) määrästä, eli siitä määrästä muistia, jonka algoritmi tarvitsee syötteen lisäksi. Tällä kierroksella oletetaan, että syötteen päälle pystyy kirjoittamaan, eli että tuloksena syntyvälle järjestetylle taulukolle ei välttämättä tarvitse varata uutta taulukkoa muistista 8/80

Vakaus (t. stabiilius) Määritelmä Järjestämisalgoritmi on vakaa (tai stabiili) (engl. stable) jos se pitää yhtäsuurten alkioiden keskinäisen järjestyksen ennallaan Scalan 2.11.8 metodit sorted ja sortwith toteuttavat stabiilin järjestämisalgoritmin. Myös sorted metodilla toteutettu sortby on stabiili. Esimerkiksi parit v a l l = L i s t ( ( " c ", 2 ), ( " b ", 3 ), ( " e ", 2 ), ( " a ", 3 ), ( " b ", 2 ), ( " a ", 2 ) ) voidaan järjestää sanakirjajärjestykseen (ensimmäinen kenttä merkitsevämpi) järjestämällä ensin toisen kentän mukaan scala > v a l tmp = l. sortby ( _. _2 ) tmp = L i s t ( ( c, 2 ), ( e, 2 ), ( b, 2 ), ( a, 2 ), ( b, 3 ), ( a, 3 ) ) ja sitten (stabiililla algoritmilla) ensimmäisen mukaan scala > v a l r e s u l t = tmp. sortby ( _. _1 ) r e s u l t = L i s t ( ( a, 2 ), ( a, 3 ), ( b, 2 ), ( b, 3 ), ( c, 2 ), ( e, 2 ) ) koska stabiili algoritmi ei vaihda esimerkiksi alkioiden (a,3) ja (a,2) keskinäistä järjestystä vaikka niiden ensimmäiset kentät ovat samat 9/80

C++-kielen (C++11) algorithm-kirjasto sisältää eri funktiot stabiiliin järjestämiseen (stable_sort-funktio) ja ei-välttämättä-stabiiliin järjestämiseen (sort-funktio) Jälkimmäisellä on hieman pienempi asymptoottinen pahimman tapauksen aikavaativuus silloin kun saatavilla ei ole tarpeeksi muistia aputaulukon varaamiseksi 10/80

Lisäysjärjestäminen 11/80

Erittäin yksinkertainen järjestämisalgoritmi, (engl. insertion sort) Tehokas vain hyvin pienille taulukoille Idea: Oletetaan, että taulukon i ensimmäistä alkiota ovat jo järjestyksessä; ensimmäisellä kierroksella i = 1 Otetaan alkio i + 1 ja etsitään mihin kohtaan j se kuuluisi jo järjestetyssä taulukon alkuosassa Siirretään alkioita j + 1,..., i yksi askel oikealle Lisätään alkio kohtaan j Nyt ensimmäiset i + 1 alkiota ovat järjestyksessä Toistetaan, kunnes koko taulukko on järjestyksessä Esimerkki: Taulukko alussa a = [a 0,a 1,a 2,a 3 ] a 23 17 25 18 12/80

Erittäin yksinkertainen järjestämisalgoritmi, (engl. insertion sort) Tehokas vain hyvin pienille taulukoille Idea: Oletetaan, että taulukon i ensimmäistä alkiota ovat jo järjestyksessä; ensimmäisellä kierroksella i = 1 Otetaan alkio i + 1 ja etsitään mihin kohtaan j se kuuluisi jo järjestetyssä taulukon alkuosassa Siirretään alkioita j + 1,..., i yksi askel oikealle Lisätään alkio kohtaan j Nyt ensimmäiset i + 1 alkiota ovat järjestyksessä Toistetaan, kunnes koko taulukko on järjestyksessä Esimerkki: Ensimmäisen kierroksen alussa osataulukko [a 0 ], sinisellä, on järjestyksessä Siirretään seuraavaa alkiota 17 vasemmalle kunnes se on oikeassa kohdassa Tässä siis siirretään 23 kohtaan 1 koska 23 > 17 ja 17 kohtaan 0 a 23 17 25 18 13/80

Erittäin yksinkertainen järjestämisalgoritmi, (engl. insertion sort) Tehokas vain hyvin pienille taulukoille Idea: Oletetaan, että taulukon i ensimmäistä alkiota ovat jo järjestyksessä; ensimmäisellä kierroksella i = 1 Otetaan alkio i + 1 ja etsitään mihin kohtaan j se kuuluisi jo järjestetyssä taulukon alkuosassa Siirretään alkioita j + 1,..., i yksi askel oikealle Lisätään alkio kohtaan j Nyt ensimmäiset i + 1 alkiota ovat järjestyksessä Toistetaan, kunnes koko taulukko on järjestyksessä Esimerkki: Toiden kierroksen alussa osataulukko [a 0,a 1 ] on järjestyksessä Siirretään alkiota 25 vasemmalle kunnes se on oikeassa kohdassa Eli ei tehdä mitään koska 23 25 a 17 23 25 18 14/80

Erittäin yksinkertainen järjestämisalgoritmi, (engl. insertion sort) Tehokas vain hyvin pienille taulukoille Idea: Oletetaan, että taulukon i ensimmäistä alkiota ovat jo järjestyksessä; ensimmäisellä kierroksella i = 1 Otetaan alkio i + 1 ja etsitään mihin kohtaan j se kuuluisi jo järjestetyssä taulukon alkuosassa Siirretään alkioita j + 1,..., i yksi askel oikealle Lisätään alkio kohtaan j Nyt ensimmäiset i + 1 alkiota ovat järjestyksessä Toistetaan, kunnes koko taulukko on järjestyksessä Esimerkki: Nyt osataulukko [a 0,a 1,a 2 ] on järjestyksessä Siirretään 18 vasemmalle kunnes se on oikeassa kohdassa Eli siirretään 25 kohtaan 3 koska 25 > 18, 23 kohtaan 2 koska 23 > 18 ja 18 kohtaan 1 a 17 23 25 18 15/80

Erittäin yksinkertainen järjestämisalgoritmi, (engl. insertion sort) Tehokas vain hyvin pienille taulukoille Idea: Oletetaan, että taulukon i ensimmäistä alkiota ovat jo järjestyksessä; ensimmäisellä kierroksella i = 1 Otetaan alkio i + 1 ja etsitään mihin kohtaan j se kuuluisi jo järjestetyssä taulukon alkuosassa Siirretään alkioita j + 1,..., i yksi askel oikealle Lisätään alkio kohtaan j Nyt ensimmäiset i + 1 alkiota ovat järjestyksessä Toistetaan, kunnes koko taulukko on järjestyksessä Esimerkki: Koko taulukko on nyt järjestyksessä a 17 18 23 25 16/80

Pseudokoodina: Insertion-sort(A): for i 1 until A.length: e A[i] // element to be moved j i while j > 0 and A[j 1] > e: A[j] A[j 1] j j 1 A[j] e Huom: toisin kuin kirjassa Introduction to Algorithms, 3rd ed. (online via Aalto lib), tässä taulukon indeksointi on alkaa indeksistä 0 17/80

Toteutus kokonaislukutaulukoille Scala-kielellä: def i n s e r t i o n S o r t ( a : Array [ I n t ] ) : Unit = { var i = 1 while ( i < a. length ) { val key = a ( i ) var j = i ; while ( j > 0 && a ( j 1) > key ) { a ( j ) = a ( j 1) j = 1 } a ( j ) = key i += 1 } } 18/80

Algoritmin ominaisuuksia: Toimii paikallaan koska vain kulloinkin siirrettävä ja oikealle siirtyvä alkio on talletettu taulukon ulkopuoliseen muistiin Stabiili koska siirrettävä alkio siirretään aina viimeisimpään mahdolliseen kohtaan eli se ei hyppää samanarvoisten alkioiden ohi 19/80

Analyysiä Parhaan tapauksen ajoaika on Θ(n): taulukoilla, jotka ovat jo järjestyksessä, yhtään alkiota ei siirretä ja n 1 alkion kohdalla tehdään yksi vertailu edeltävän alkion kanssa Pahimman tapauksen ajoaika on Θ(n 2 ): Tarkastellaan taulukkoa, joka on alussa käänteisessä järjestyksessä toista alkiota siirretään 1 askel kolmatta alkiota 2 askelta... n:ttä alkiota siirretään n 1 askelta Jokainen i askeleen siirto aiheuttaa i:n muun alkion siirtämisen oikealle kierros i suorittaa Θ(i) vertailua ja taulukon luku/kirjoitusoperaatiota T(n) = Θ(n) + T(n 1) = Θ(n 2 ) Samat parhaan ja pahimman tapauksen rajat koskevat myös tehtyjen vertailujen ja muistioperaatioiden määrää 20/80

Entäpä keskimääräinen ajoaika? Tarkastellaan taulukkoa, jossa n erisuurta satunnaisesti valittua alkiota Jokaisella kierroksella i, siirrettäessä alkiota a i oikealle paikalleen, osataulukko [a 0,...,a i 1 ] sisältää keskimäärin i/2 alkiota, jotka ovat pienempiä kuin a i keskimääräinen ajoaika on n 1 i=1 i/2 = Θ(n2 ) Näissä analyyseissa on siis oletettu, että kahden alkion vertailu sekä alkion luku taulukosta ja kirjoitus taulukkoon ovat vakioaikaisia operaatioita 21/80

Entäpä suorituskyky jossain sovelluksessa? Riippuu sovelluksen tuottamista syötetaulukoista Jos syötetaulukko on aina melkein järjestetty, lisäysjärjestäminen saatta toimia hyvinkin käytännössä Jos syötetaulukot ovat satunnaisesti järjestettyjä tai sellaisia, joissa monia alkioita täytyy siirtää pitkiä matkoja, kannattaa varmaan käyttää jotain muuta algoritmia Huom: lisäysjärjestämisalgoritmin koodi on hyvin kompakti eli siinä olevat vakiot ovat pieniä ja lisäksi peräkkäiset muistiviittaukset viittaavat yleensä lähellä olevaan kohtaan taulukossa lisäysjärjestäminen voi olla kilpailukykyinen (hyvin) pienille taulukoille Eräs mahdollinen optimointi: jos alkioiden vertailu onkin aikaavievempää, voitaisiin käyttää puolitushakua oikean siirtokohdan löytämiseksi Vertailujen lukumäärä pahimmassakin tapauksessa tippuu O(nlog 2 n) Mutta kokonaisajoaika säilyy Θ(n 2 ) koska siirrot oikealle täytyy edelleen tehdä 22/80

Yleiset tyypit Geneerinen versio lisäysjärjestämisestä mielivaltaisille vertailtaville alkiotyypeille: def i n s e r t i o n S o r t [ A <% Ordered [ A ] ] ( a : Array [ A ] ) = { var i = 1 while ( i < a. length ) { val key = a ( i ) var j = i ; while ( j > 0 && a ( j 1) > key ) { a ( j ) = a ( j 1) j = 1 } a ( j ) = key i += 1 } } Valitettavasti nykyisellä Scala-kääntäjällä yllä olevan yleistetyn koodin ajoaika on huomattavasti huonompi (ks seuraava kalvo) 23/80

Lisäysjärjestäminen taulukoille, joissa satunnaisia kokonaislukuja Jotta voidaan käyttää Ordered-piirteen vertailuoperaatiota, jokainen taulukosta haettu kokonaisluku muutetaan olioksi ( boxing ), mikä aiheuttaa muistin varaamista ja myöhemmin vastaavasti roskankeruualgoritmin ajon 24/80

Lomitusjärjestäminen 25/80

Lomitusjärjestäminen on yksi käytetyimmistä järjestämisalgoritmeista Pahimman tapauksen ajoaika on O(nlog 2 n) Idea: Kahden jo järjestetyn taulukun yhdistäminen eli lomittaminen yhdeksi kummankin alkiot sisältäväksi järjestetyksi taulukoksi on helppoa Niinpä voidaan rekursiivisesti jakaa syötetaulukko pienemmiksi peräkkäisiksi osataulukoiksi kunnes ne on helppoa/triviaalia järjestää (esim. kunnes osataulukkossa on vain yksi alkio) ja sitten lomittaa saadut järjestetyt osataulukot pidemmiksi ja pidemmiksi kunnes koko taulukko on järjestetty Lomitusjärjestäminen on täten hajauta ja hallitse-tyyppinen algoritmi. Niissä on yleensä 3 vaihetta: hajoita (engl. divide): ongelma hajoitetaan rekursiivisesti pienempiin osaongelmiin kunnes niistä tulee helppoja hallitse (engl. conquer): pienet osaongelmat ratkaistaan yhdistä (engl. combine): osaongelmien ratkaisut yhdistetään niin, että saadaan ratkaisu koko alkuperäiseen ongelmaan 26/80

Esimerkki: Pienehkön taulukon järjestäminen lomitusjärjestämisellä, idea korkealla tasolla: original 21 17 19 1 21 7 2 20 divide 21 17 19 1 21 7 2 20 divide divide 21 17 19 1 21 7 2 20 divide divide divide divide 21 17 19 1 21 7 2 20 merge merge merge merge 17 21 1 19 7 21 2 20 merge merge 1 17 19 21 2 7 20 21 merge sorted 1 2 7 17 19 20 21 21 27/80

Lomitusoperaation toteuttaminen Hajoita-vaiheessa taulukko hajoitetaan pienempiin peräkkäisiin osataulukoihin. Huom: näitä osataulukkoja ei tarvitse erikseen tallettaa mihinkään vaan riittää pitää kirjaa niiden alku- ja loppuindekseistä. Lomitusvaiheessa kulloinkin yhdistettävät 2 osataulukkoa ovat myös peräkkäisiä Yhdistetty osataulukko muodostetaan yleensä ylimääräiseen aputaulukkoon ja kopioidaan sieltä sitten takaisin alkuperäiseen Lomitusalgoritmi pitää yllä kahta indeksiä, i ja j, yhdistettävien osataulukkojen alusta alkaen ja jokaisella askeleella kopioi näiden viittaamista alkioista pienemmän aputaulukkoon ja lisää kyseistä indeksiä yhdellä Esimerkki: Kahden peräkkäisen järjestetyn osataulukon yhdistäminen Oletetaan, että ollaan jo järjestetty peräkkäiset osataulukot [start,mid-1]=[0,1] ja [mid,end]=[2,3] start mid end original 17 21 1 19 21 7 2 20 i j aux dest 28/80

Lomitusoperaation toteuttaminen Hajoita-vaiheessa taulukko hajoitetaan pienempiin peräkkäisiin osataulukoihin. Huom: näitä osataulukkoja ei tarvitse erikseen tallettaa mihinkään vaan riittää pitää kirjaa niiden alku- ja loppuindekseistä. Lomitusvaiheessa kulloinkin yhdistettävät 2 osataulukkoa ovat myös peräkkäisiä Yhdistetty osataulukko muodostetaan yleensä ylimääräiseen aputaulukkoon ja kopioidaan sieltä sitten takaisin alkuperäiseen Lomitusalgoritmi pitää yllä kahta indeksiä, i ja j, yhdistettävien osataulukkojen alusta alkaen ja jokaisella askeleella kopioi näiden viittaamista alkioista pienemmän aputaulukkoon ja lisää kyseistä indeksiä yhdellä Esimerkki: Kahden peräkkäisen järjestetyn osataulukon yhdistäminen Verrataan alkioita 17 ja 1 original start mid end 17 21 1 19 21 7 2 20 Alkio 1 kopioidaan aputaulukkoon aux i dest j 29/80

Lomitusoperaation toteuttaminen Hajoita-vaiheessa taulukko hajoitetaan pienempiin peräkkäisiin osataulukoihin. Huom: näitä osataulukkoja ei tarvitse erikseen tallettaa mihinkään vaan riittää pitää kirjaa niiden alku- ja loppuindekseistä. Lomitusvaiheessa kulloinkin yhdistettävät 2 osataulukkoa ovat myös peräkkäisiä Yhdistetty osataulukko muodostetaan yleensä ylimääräiseen aputaulukkoon ja kopioidaan sieltä sitten takaisin alkuperäiseen Lomitusalgoritmi pitää yllä kahta indeksiä, i ja j, yhdistettävien osataulukkojen alusta alkaen ja jokaisella askeleella kopioi näiden viittaamista alkioista pienemmän aputaulukkoon ja lisää kyseistä indeksiä yhdellä Esimerkki: Kahden peräkkäisen järjestetyn osataulukon yhdistäminen Verrataan alkioita 17 ja 19 original start mid end 17 21 1 19 21 7 2 20 Alkio 17 kopioidaan aputaulukkoon aux i 1 j dest 30/80

Lomitusoperaation toteuttaminen Hajoita-vaiheessa taulukko hajoitetaan pienempiin peräkkäisiin osataulukoihin. Huom: näitä osataulukkoja ei tarvitse erikseen tallettaa mihinkään vaan riittää pitää kirjaa niiden alku- ja loppuindekseistä. Lomitusvaiheessa kulloinkin yhdistettävät 2 osataulukkoa ovat myös peräkkäisiä Yhdistetty osataulukko muodostetaan yleensä ylimääräiseen aputaulukkoon ja kopioidaan sieltä sitten takaisin alkuperäiseen Lomitusalgoritmi pitää yllä kahta indeksiä, i ja j, yhdistettävien osataulukkojen alusta alkaen ja jokaisella askeleella kopioi näiden viittaamista alkioista pienemmän aputaulukkoon ja lisää kyseistä indeksiä yhdellä Esimerkki: Kahden peräkkäisen järjestetyn osataulukon yhdistäminen Verrataan alkioita 21 ja 19 Alkio 19 kopioidaan aputaulukkoon original aux start mid end 17 21 1 19 21 7 2 20 i 1 17 dest j 31/80

Lomitusoperaation toteuttaminen Hajoita-vaiheessa taulukko hajoitetaan pienempiin peräkkäisiin osataulukoihin. Huom: näitä osataulukkoja ei tarvitse erikseen tallettaa mihinkään vaan riittää pitää kirjaa niiden alku- ja loppuindekseistä. Lomitusvaiheessa kulloinkin yhdistettävät 2 osataulukkoa ovat myös peräkkäisiä Yhdistetty osataulukko muodostetaan yleensä ylimääräiseen aputaulukkoon ja kopioidaan sieltä sitten takaisin alkuperäiseen Lomitusalgoritmi pitää yllä kahta indeksiä, i ja j, yhdistettävien osataulukkojen alusta alkaen ja jokaisella askeleella kopioi näiden viittaamista alkioista pienemmän aputaulukkoon ja lisää kyseistä indeksiä yhdellä Esimerkki: Kahden peräkkäisen järjestetyn osataulukon yhdistäminen Oikean osataulukon indeksi j on nyt osataulukon ulkopuolella Kopioidaan vasemman osataulukon loput alkiot aputaulukkoon original aux start mid end 17 21 1 19 21 7 2 20 i 1 17 19 dest j 32/80

Lomitusoperaation toteuttaminen Hajoita-vaiheessa taulukko hajoitetaan pienempiin peräkkäisiin osataulukoihin. Huom: näitä osataulukkoja ei tarvitse erikseen tallettaa mihinkään vaan riittää pitää kirjaa niiden alku- ja loppuindekseistä. Lomitusvaiheessa kulloinkin yhdistettävät 2 osataulukkoa ovat myös peräkkäisiä Yhdistetty osataulukko muodostetaan yleensä ylimääräiseen aputaulukkoon ja kopioidaan sieltä sitten takaisin alkuperäiseen Lomitusalgoritmi pitää yllä kahta indeksiä, i ja j, yhdistettävien osataulukkojen alusta alkaen ja jokaisella askeleella kopioi näiden viittaamista alkioista pienemmän aputaulukkoon ja lisää kyseistä indeksiä yhdellä Esimerkki: Kahden peräkkäisen järjestetyn osataulukon yhdistäminen Lopuksi järjestetty osataulukko kopioidaan aputaulukosta takaisin alkuperäiseen start mid end original 1 17 19 21 21 7 2 20 aux 1 17 19 21 33/80

Lomitusalgoritmin toteutus Scala-kielellä: def merge ( a : Array [ I n t ], aux : Array [ I n t ], s t a r t : I n t, mid : I n t, end : I n t ) : Unit = { var ( i, j, dest ) = ( s t a r t, mid, s t a r t ) while ( i < mid && j <= end ) { / / Merge to aux, smallest f i r s t i f ( a ( i ) <= a ( j ) ) { aux ( dest ) = a ( i ) ; i += 1 } else { aux ( dest ) = a ( j ) ; j += 1 } dest += 1 } while ( i < mid ) { aux ( dest ) = a ( i ) ; i += 1; dest += 1 } / / Copy r e s t while ( j <= end ) { aux ( dest ) = a ( j ) ; j += 1; dest += 1 } / / Copy r e s t dest = s t a r t / / Copy from aux back to a while ( dest <= end ) { a ( dest ) = aux ( dest ) ; dest += 1 } } Algoritmin ajoaika on selvästikin Θ(k), missä k on kahden yhdistettävän osataulukon yhteenlaskettu koko 34/80

Lomitusoperaatiota alifunktiona käyttäen itse rekursiivinen lomitusjärjestämisalgoritmi on varsin yksinkertainen ja elegantti: def mergesort ( a : Array [ I n t ] ) : Unit = { i f ( a. length <= 1) return } / / A u x i l i a r y memory f o r doing merges val aux = new Array [ I n t ] ( a. l ength ) / / The r e c u r s i v e main a l g o r i t h m def _mergesort ( s t a r t : I n t, end : I n t ) : Unit = { i f ( s t a r t >= end ) return / / One or zero elements, do nothing val l e f t E n d = s t a r t + ( end s t a r t ) / 2 / / The midpoint _mergesort ( s t a r t, l e f t E n d ) / / Sort the l e f t h a l f _mergesort ( l e f t E n d + 1, end ) / / Sort the r i g h t h a l f merge ( a, aux, s t a r t, l e f t E n d + 1, end ) / / Merge the r e s u l t s } _mergesort ( 0, a. length 1) Algoritmi yllä jälleen kokonaislukutaulukoille, yleistäminen muille ja yleisille Ordered-piirteen toteuttaville tyypeille helppoa 35/80

Ajoaika-analyysiä Lomitusjärjestämisen ajoaika saadaan rekursioyhtälöstä T(n) = T( n/2 ) + T( n/2 ) + cn missä c on jälleen vakio Jos n on kahden potenssi, voidaan tehdä sijoitus n = 2 k ja saadaan T(2 k ) = T(2 k 1 ) + T(2 k 1 ) + c2 k = c2 k + 2T(2 k 1 ) Laajentamalla rekursiyhtälöä saadaan T(2 k ) = c2 k + 2(c2 k 1 + 2T(2 k 2 )) = c2 k + 2c2 k 1 + 4(c2 k 2 + 2T(2 k 3 )) =... = kc2 k Koska k = log 2 n, T(n) = cnlog 2 n = Θ(nlog 2 n) 36/80

Ajoaikaa voidaan visualisoida myös graafisesti seuraavasti esittämällä arvon T(n) laskenta puuna: Total time at level T (n) cn nc log 2 n levels T (n/2) c n 2 T (n/2) c n 2 T (n/4) c n 4 T (n/4) c n 4 T (n/4) c n 4 T (n/4) c n 4 nc nc T (1) c T (1) c T (1) c T (1) c T (1) c T (1) c T (1) c T (1) c nc Tässä on abstrahoitu pois pyöristämiset ja käytetty rekursioyhtälöä Total in all levels: nc log 2 n T(1) = C ja T(n) = T(n/2) + T(n/2) + cn Punaiset tekstit kuvaavat solmussa ja tasolla kokonaisuudessaan tehtävää työmäärää Rekursio pysähtyy kun n 2 i 1 eli kun i log 2 n; täten puussa on log 2 n tasoa 37/80

Kokeellista analyysiä Suuremmille taulukoille lomitusjärjestäminen on huomattavasti nopeampi ja paremmin skaalautuva kuin lisäysjärjestäminen 38/80

Satunnaisia kokonaislukuja sisältävillä taulukoilla lisäysjärjestäminen on kilpailukykyinen ainoastaan erittäin pienillä taulukoilla 39/80

Pienten osataulukoiden käsitteleminen Jokainen rekursiokutsu aiheuttaa funktion kutsumisen... ja täten ylimääräisen koodin ajamista ja muistinvarausta (kutsukehyksen varaaminen ja alustus) Käytetään edellisessä vertailussa huomattua lisäysjärjestyksen suhteellista nopeutta pienillä taulukoilla hyväksi ja tehdään lomitusjärjestysalgoritmin pieni muunnos, joka ei tee rekursiota yhden kokoisiin taulukoihin asti vaan kutsuu lisäysjärjestysalgoritmia osataulukoille, jotka ovat jotain tiettyä alarajaa pienempiä 40/80

Muunnos Scala-kielellä: def mergesort ( a : Array [ I n t ], t h r e s h o l d : I n t = 64) : Unit = { i f ( a. length <= 1) return val aux = new Array [ I n t ] ( a. l ength ) def _mergesort ( s t a r t : I n t, end : I n t ) : Unit = { i f ( end s t a r t < t h r e s h o l d ) i n s e r t i o n s o r t ( a, s t a r t, end ) / / Changed else { val l e f t E n d = ( s t a r t + end ) / 2 _mergesort ( s t a r t, l e f t E n d ) _mergesort ( leftend + 1, end ) merge ( a, aux, s t a r t, l e f t E n d + 1, end ) } } _mergesort ( 0, a. length 1) } Yllä insertionsort(a, start, end) järjestää osataulukon a[start, end] lisäysjärjestysalgoritmilla 41/80

Tuloksena kohtuullinen vakiokertoiminen parannus ajoaikaan 42/80

Pikajärjestäminen 43/80

Toinen erittäin yleisesti käytetty järjestämismenetelmä (engl. quicksort) Kuten lomitusjärjestäminen, pikajärjestäminen on hajoita ja hallitse-tyyppinen algoritmi Helppo saada toimimaan paikallaan 44/80

Idea osataulukon A[l, r], missä l r, järjestämiseksi: 1. Perustapaus: jos l = r, osataulukossa A[l, r] on vain yksi alkio ja se on täten järjestetty 2. Hajoita: valitaan jokin jakoalkio (engl. pivot element) p osataulukosta A[l, r] ja ositetaan A[l, r] kolmeen osataulukkoon A[l,q 1], A[q,q] ja A[q + 1,r] siten, että kaikki A[l,q 1] alkiot ovat pienempiä tai yhtä suuria kuin p, A[q,q] = [p] ja kaikki A[q + 1,r] alkiot ovat yhtä suuria tai suurempia kuin p Tämän askeleen jälkeen p on oikealla paikallaan 3. Hallitse: järjestetään osataulukot A[l, q 1] ja A[q + 1, r] rekursiivisesti samalla lailla Pääfunktio Scala-kielellä: def q u i c k s o r t ( a : Array [ I n t ] ) : Unit = { def _ q u i c k s o r t ( l o : I n t, h i : I n t ) : Unit = { val q = p a r t i t i o n ( a, lo, h i ) i f ( l o < q 1) _ q u i c k s o r t ( lo, q 1) i f ( q + 1 < h i ) _ q u i c k s o r t ( q + 1, h i ) } i f ( a. length >= 2) _ q u i c k s o r t ( 0, a. length 1) } 45/80

Esimerkki: taulukon järjestäminen pikajärjestämisellä Edellisen kalvon Scala-toteutus Rekursiiviset kutsut ja ositusten tulokset suoritusjärjestyksessä Vihreä: osataulukko a[lo, hi] Punainen: jakoalkio Sininen: vasen osataulukko osituksen jälkeen Syaani: oikea osataulukko osituksen jälkeen quicksort(0,7) after partition(0,7) quicksort(0,4) after partition(0,4) quicksort(2,4) after partition(2,4) quicksort(2,3) after partition(2,3) quicksort(6,7) after partition(6,7) 0 1 2 3 4 5 6 7 21 17 19 1 21 7 2 20 17 19 1 7 2 20 21 21 17 19 1 7 2 20 21 21 1 2 17 7 19 20 21 21 1 2 17 7 19 20 21 21 1 2 17 7 19 20 21 21 1 2 17 7 19 20 21 21 1 2 7 17 19 20 21 21 1 2 7 17 19 20 21 21 1 2 7 17 19 20 21 21 46/80

Sama puumuodossa esitettynä; toisin kuin lomitusjärjestämisessä, kutsupuu ei ole kovinkaan tasapainoinen 0 1 2 3 4 5 6 7 input 21 17 19 1 21 7 2 20 partition(0,7) 17 19 1 7 2 20 21 21 quicksort(0,4) quicksort(6,7) 17 19 1 7 2 21 21 partition(0,4) partition(6,7) 1 2 17 7 19 21 21 quicksort(2,4) 17 7 19 partition(2,4) 17 7 19 quicksort(2,3) 17 7 partition(2,3) 7 17 sorted 1 2 7 17 19 20 21 21 47/80

Eräs osoitusalgoritmi kirjasta Introduction to Algorithms, 3rd ed. (online via Aalto lib): Syötteenä osataulukko A[lo, hi] Valitaan jokin jakoalkio p osataulukosta Vaihdetaan jakoalkio osataulukon viimeisen alkion kanssa Alkaen indeksistä lo, käytetään indeksimuuttujia i ja j s.e. osataulukon alkiot i:n vasemmalla puolella ovat korkeintaan p alkiot i,...,j 1 ovat suurempia kuin p Siirretään indeksiä j oikealle ja jos siinä oleva alkio on korkeintaan p, vaihdetaan alkiot kohdissa i ja j sekä lisätään indeksiä i yhdellä Lopuksi vaihdetaan viimeinen alkio (jakoalkio) ja alkio kohdassa i Esimerkki: Aloitustilanne lo 21 17 19 1 21 7 2 20 hi Jakoalkio p (punaisella) on osataulukon lopussa i j 21 > 20, lisätään j yhdellä 48/80

Eräs osoitusalgoritmi kirjasta Introduction to Algorithms, 3rd ed. (online via Aalto lib): Syötteenä osataulukko A[lo, hi] Valitaan jokin jakoalkio p osataulukosta Vaihdetaan jakoalkio osataulukon viimeisen alkion kanssa Alkaen indeksistä lo, käytetään indeksimuuttujia i ja j s.e. osataulukon alkiot i:n vasemmalla puolella ovat korkeintaan p alkiot i,...,j 1 ovat suurempia kuin p Siirretään indeksiä j oikealle ja jos siinä oleva alkio on korkeintaan p, vaihdetaan alkiot kohdissa i ja j sekä lisätään indeksiä i yhdellä Lopuksi vaihdetaan viimeinen alkio (jakoalkio) ja alkio kohdassa i Esimerkki: Syaanit alkiot kohdissa i,...,j 1 ovat suurempia kuin jakoalkio 17 20, vaihdetaan alkiot kohdissa i ja j Lisätään i ja j yhdellä lo 21 17 19 1 21 7 2 20 i j hi 49/80

Eräs osoitusalgoritmi kirjasta Introduction to Algorithms, 3rd ed. (online via Aalto lib): Syötteenä osataulukko A[lo, hi] Valitaan jokin jakoalkio p osataulukosta Vaihdetaan jakoalkio osataulukon viimeisen alkion kanssa Alkaen indeksistä lo, käytetään indeksimuuttujia i ja j s.e. osataulukon alkiot i:n vasemmalla puolella ovat korkeintaan p alkiot i,...,j 1 ovat suurempia kuin p Siirretään indeksiä j oikealle ja jos siinä oleva alkio on korkeintaan p, vaihdetaan alkiot kohdissa i ja j sekä lisätään indeksiä i yhdellä Lopuksi vaihdetaan viimeinen alkio (jakoalkio) ja alkio kohdassa i Esimerkki: Siniset alkiot kohdissa lo,...,i 1 ovat korkeintaan yhtä suuria kuin p 19 20, vaihdetaan alkiot kohdissa i ja j Lisätään i ja j yhdellä lo 17 21 19 1 21 7 2 20 i j hi 50/80

Eräs osoitusalgoritmi kirjasta Introduction to Algorithms, 3rd ed. (online via Aalto lib): Syötteenä osataulukko A[lo, hi] Valitaan jokin jakoalkio p osataulukosta Vaihdetaan jakoalkio osataulukon viimeisen alkion kanssa Alkaen indeksistä lo, käytetään indeksimuuttujia i ja j s.e. osataulukon alkiot i:n vasemmalla puolella ovat korkeintaan p alkiot i,...,j 1 ovat suurempia kuin p Siirretään indeksiä j oikealle ja jos siinä oleva alkio on korkeintaan p, vaihdetaan alkiot kohdissa i ja j sekä lisätään indeksiä i yhdellä Lopuksi vaihdetaan viimeinen alkio (jakoalkio) ja alkio kohdassa i Esimerkki: Siniset alkiot kohdissa lo,...,i 1 ovat korkeintaan yhtä suuria kuin p 1 20, vaihdetaan alkiot kohdissa i ja j Lisätään i ja j yhdellä lo 17 19 21 1 21 7 2 20 i j hi 51/80

Eräs osoitusalgoritmi kirjasta Introduction to Algorithms, 3rd ed. (online via Aalto lib): Syötteenä osataulukko A[lo, hi] Valitaan jokin jakoalkio p osataulukosta Vaihdetaan jakoalkio osataulukon viimeisen alkion kanssa Alkaen indeksistä lo, käytetään indeksimuuttujia i ja j s.e. osataulukon alkiot i:n vasemmalla puolella ovat korkeintaan p alkiot i,...,j 1 ovat suurempia kuin p Siirretään indeksiä j oikealle ja jos siinä oleva alkio on korkeintaan p, vaihdetaan alkiot kohdissa i ja j sekä lisätään indeksiä i yhdellä Lopuksi vaihdetaan viimeinen alkio (jakoalkio) ja alkio kohdassa i Esimerkki: Jatketaan kunnes j = r lo 17 19 1 7 2 21 21 20 hi Siniset alkiot kohdissa lo,...,i 1 ovat korkeintaan yhtä suuria kuin p Syaanit alkiot kohdissa i,...,j 1 ovat suurempia kuin jakoalkio i j 52/80

Eräs osoitusalgoritmi kirjasta Introduction to Algorithms, 3rd ed. (online via Aalto lib): Syötteenä osataulukko A[lo, hi] Valitaan jokin jakoalkio p osataulukosta Vaihdetaan jakoalkio osataulukon viimeisen alkion kanssa Alkaen indeksistä lo, käytetään indeksimuuttujia i ja j s.e. osataulukon alkiot i:n vasemmalla puolella ovat korkeintaan p alkiot i,...,j 1 ovat suurempia kuin p Siirretään indeksiä j oikealle ja jos siinä oleva alkio on korkeintaan p, vaihdetaan alkiot kohdissa i ja j sekä lisätään indeksiä i yhdellä Lopuksi vaihdetaan viimeinen alkio (jakoalkio) ja alkio kohdassa i Esimerkki: Vaihdetaan viimeinen alkio (jakoalkio) ja alkio kohdassa i Lopetetaan lo 17 19 1 7 2 20 21 21 i hi j 53/80

Toteutus Scala-kielellä: def swap ( a : Array [ I n t ], i : I n t, j : I n t ) : Unit = { val t = a ( i ) ; a ( i ) = a ( j ) ; a ( j ) = t } def p a r t i t i o n ( a : Array [ I n t ], l o : I n t, h i : I n t ) : I n t = { val p i v o t = a ( h i ) / / Very simple p i v o t s e l e c t i o n! var i = l o 1 var j = l o while ( j < h i ) { i f ( a ( j ) <= p i v o t ) { i += 1; swap ( a, i, j ) } j += 1 } swap ( a, i + 1, h i ) i + 1 } 54/80

Kokeellinen vertailu Lomitus- ja pikajärjestäminen taulukoilla, joissa satunnaisia kokonaislukuja: 55/80

Lisäksi vielä Scala-standardikirjaston Array[Int].sorted ja java.util.arrays.sort 56/80

Tässä käytetty funktion java.util.arrays.sort toteutus on eräs pikajärjestämisalgoritmin versio (ks. erään Java-toteutuksen lähdekoodi) Tämänhetkinen toteutus Array[Int].sorted-metodissa muuntaa kokonaislukutaulukon taulukoksi olioita ja kutsuu java.util.arrays.sort-funktiota oliotaulukoille; tämä taas toteuttaa TimSort -algoritmin (ks. erään Java-toteutuksen lähdekoodi) 57/80

Ajoaikavaativuus Edellisten kokeellisten tulosten perusteella pikajärjestämisen ajoaika on varmaankin O(nlog 2 n)? Esitetyllä perusversiolla asia ei kuitenkaan ole näin vaan pahimman tapauksen ajoaika on Θ(n 2 ) Ositusfunktion ajoaika on Θ(k), missä k on tarkasteltavan osataulukon koko Perusversiossa (Scala-koodi) valittiin aina osataulukon viimeinen alkio jakoalkioksi Neliöllinen pahimman tapauksen ajoaika realisoituu kun taulukko on jo alunperin järjestyksessä Jakoalkio ei liiku mihinkään ja vasemmanpuoleinen k 1 alkion osataulukko on sama kuin ennen ositusta Rekursioyhtälöllä saadaan T(n) = Θ(n) + T(n 1) = Θ(n 2 ) 58/80

Jakoalkion valinta Neliöllinen ajoaika jo järjestetyillä taulukoilla ei ole hyvä asia: käytännössä datamme voi usein olla jo (melkein) järjestyksessä Täten jakoalkion valinta on tärkeä asia O(nlog 2 n) saadaan kun valitaan jakoalkio niin, että syntyvät vasen ja oikea osataulukko ovat mahdollisimman samankokoisia Eli jakoalkioksi kannattaisi valita osataulukon mediaani Periaatteessa mediaani voidaan laskea lineaarisessa ajassa (ks. luku 9.3 kirjassa Introduction to Algorithms, 3rd ed. (online via Aalto lib)) mutta tähän liittyvät vakiot ovat melko suuria Käytännöllisempi approksimaatio on ottaa jakoalkioksi osataulukon 3 satunnaisesti valitun alkion mediaani 59/80

Toinen yleinen ja helppo strategia on valita jakoalkio satunnaisesti tarkasteltavasta osataulukosta Taulukoille, joissa on n erisuuruista alkiota, pikalajittelun odotettu ajoaika on O(nlog 2 n) kun valitaan satunnainen jakoalkio (kappale 7.4 kirjassa Introduction to Algorithms, 3rd ed. (online via Aalto lib)) Vastaavaa tapahtui kokeellisissa tuloksissa aiemmin: niissä ei valittu satunnaista jakoalkiota mutta alkiot olivat satunnaisia ja siksi tilanne oli käytännössä sama ja ajoaika oli O(nlog 2 n) 60/80

Yhtäsuuret alkiot Toinen ongelma pikajärjestämisalgoritmin perusversiolle muodostuu yhtäsuurista alkioista Tarkastellaan taulukkoa, jossa on n alkiota ja kaikki alkiot ovat samoja Riippumatta jakoalkion valinnasta, k-alkioisen osataulukon ositus tuottaa jakoalkion sekä yhden uuden alitaulukon, jonka koko on k 1 Tällöin kokonaisajoaika on taas Θ(n 2 ) Tämä ongelma voidaan ratkaista paremmalla ositusalgoritmilla, joka tuottaa 3 uutta osataulukkoa: ensimmäisen alkiot ovat pienempiä kuin jakoalkio, toisen, sisältäen jakoalkion, ovat yhtä suuria ja kolmannen ovat suurempia yhtäsuuria alkiota sisältävä osataulukko on nyt järjestetty eikä sitä tarvitse enää käsitellä jatkossa toteutus ohjelmointitehtävässä 61/80

Asymptoottisia alarajoja 62/80

Kuinka kaukana optimaalisesta esimerkiksi lomitusjärjestäminen on? Jotta tähän voidaan vastata, täytyy määrittää käytetty laskennan malli Oletataan normaali random access machine -malli, missä muistiosoitteisiin voidaan viitata vakioajassa ja missä muistiosoitteisiin ja rekistereihin mahtuu tietty kiinteä määrä bittejä (32, 64 tms) Yksittäinen laskennan askel voi käsitellä vain jotain vakiomäärää bittejä Oletetaan lisäksi vain vertailuja -rajoitus, missä 1. kahta alkiota voidaan vertailla operaatioilla =, <,, > ja 2. mutta alkioista ei voida käyttää muuta tietoa, kuten esimerkiksi niiden bittitason koodausta Esimerkiksi järjestämisalgoritmitoteutukset, jotka käyttävät alkioiden vertailuun vain Scala-kielen Ordered-piirrettä, kuuluvat tähän luokkaan 63/80

Tällaisessa vain vertailuja -mallissa voidaan todistaa asymptoottiset alarajat sille, kuinka monta vertailua mikä tahansa algoritmi joutuu pahimmassa tapauksessa suorittamaan Alkion hakeminen järjestetystä taulukosta vaatii Ω(log 2 n) vertailua pahimmassa tapauksessa Täten puolitushakualgoritmi on, asymptoottisessa mielessä, optimaalinen n-alkioisen taulukon järjestäminen vaatii Ω(nlog 2 n) vertailua pahimassa tapauksessa Täten lomitusjärjestäminen on asymptoottisessa mielessä optimaalinen Todistukset: kappale 8.1 kirjassa Introduction to Algorithms, 3rd ed. (online via Aalto lib) 64/80

Laskemis- ja kantalukujärjestäminen 65/80

Useille alkiotyypeille voidaan tehdä järjestämisalgoritmeja, jotka eivät pohjaudu alkioiden suoraan vertailuun Toisin sanoen voidaan käyttää alkioiden ominaisuuksia hyväksi ja saada aikaan nopeampia järjestämisalgoritmeja Seuraavassa tarkastellaan laskemisjärjestämistä (engl. counting sort), jota voidaan käyttää pienille kokonaisluvuille kuvautuville alkiojoukoille, sekä kantalukujärjestämistä, joka käyttää hyväkseen alkioiden binääri-, merkkijono-, tms esitystä 66/80

Laskemisjärjestäminen Oletetaan, että esiintyvät alkiot ovat lukuja {0,..., k 1} jollekin kohtuullisen pienelle arvolle k (tai helposti kuvattavissa tälle joukolle) Esimerkiksi jos tahdotaan järjestää taulukollinen tavuja, niin k = 256 Laskemisjärjestämisen ideana on ensin laskea jokaisen alkion esiintymismäärä taulukossa ja sitten käyttää esiintymismäärien kumulatiivista määrää siirrettäessä jokainen alkio yksi kerrallaan oikealle paikalle Käyttää lisämuistia määrän Θ(n + k) eli ei toimi paikallaan: Θ(k) tavua taulukolle, johon esiintymismäärät lasketaan Θ(n) tavua tulostaulukolle 67/80

Esimerkki: Oktaalijärjestelmän numeroita sisältävän taulukon järjestäminen original counting-sort(a): 0 1 2 3 4 5 6 7 8 9 10 11 2 3 2 0 7 7 2 3 1 4 6 0 // 1: Allocate and init arrays count int-array of length k 0 1 2 3 4 5 6 7 result int-array of length A.length count 0 0 0 0 0 0 0 0 for v in 0 until k: count[v] 0 // 2: Count occurrences for i in 0 until A.length: 0 1 2 3 4 5 6 7 8 9 10 11 count[a[i]] +=1 result // 3: Cumulative occurrences cumu 0 Kohta 1 for v in 0 until k: current count[v] Varataan apu- ja tulostaulukot count[v] cumu Alustetaan aputaulukko count arvoilla 0 cumu+=current // 4: Make the result Vie ajan Θ(k) for i in 0 until A.length: result[count[a[i]]] A[i] Tässä k = 8 count[a[i]] +=1 68/80

Esimerkki: Oktaalijärjestelmän numeroita sisältävän taulukon järjestäminen original counting-sort(a): 0 1 2 3 4 5 6 7 8 9 10 11 2 3 2 0 7 7 2 3 1 4 6 0 // 1: Allocate and init arrays count int-array of length k 0 1 2 3 4 5 6 7 result int-array of length A.length count 2 1 3 2 1 0 1 2 for v in 0 until k: count[v] 0 // 2: Count occurrences for i in 0 until A.length: count[a[i]] +=1 // 3: Cumulative occurrences cumu 0 for v in 0 until k: current count[v] count[v] cumu cumu+=current // 4: Make the result for i in 0 until A.length: result[count[a[i]]] A[i] count[a[i]] +=1 0 1 2 3 4 5 6 7 8 9 10 11 Kohta 2 result Lasketaan n askeleella jokaiseen alkioon count[j] kuinka monta kertaa alkio j esiintyy syötetaulukossa Vie ajan Θ(n) 69/80

Esimerkki: Oktaalijärjestelmän numeroita sisältävän taulukon järjestäminen original counting-sort(a): 0 1 2 3 4 5 6 7 8 9 10 11 2 3 2 0 7 7 2 3 1 4 6 0 // 1: Allocate and init arrays count int-array of length k 0 1 2 3 4 5 6 7 result int-array of length A.length count 2 1 3 2 1 0 1 2 0 1 2 3 4 5 6 7 for v in 0 until k: count[v] 0 index 0 2 3 6 8 9 9 10 // 2: Count occurrences for i in 0 until A.length: count[a[i]] +=1 // 3: Cumulative occurrences cumu 0 for v in 0 until k: current count[v] count[v] cumu cumu+=current // 4: Make the result for i in 0 until A.length: result[count[a[i]]] A[i] count[a[i]] +=1 0 1 2 3 4 5 6 7 8 9 10 11 Kohta 3 result Jokaiselle arvolle j lasketaan luku index[j] = j 1 l=0 count[l], joka kertoo ensimmäisen arvon j esiintymiskohdan järjestetyssä tulostaulukossa Huom: pseudokoodissa tämä lasketaan taulukkoon count tilan säästämiseksi Vie ajan Θ(k) 70/80

Esimerkki: Oktaalijärjestelmän numeroita sisältävän taulukon järjestäminen original counting-sort(a): 0 1 2 3 4 5 6 7 8 9 10 11 2 3 2 0 7 7 2 3 1 4 6 0 // 1: Allocate and init arrays count int-array of length k 0 1 2 3 4 5 6 7 result int-array of length A.length count 2 1 3 2 1 0 1 2 0 1 2 3 4 5 6 7 for v in 0 until k: count[v] 0 index 0 2 3 6 8 9 9 10 // 2: Count occurrences for i in 0 until A.length: count[a[i]] +=1 // 3: Cumulative occurrences cumu 0 for v in 0 until k: current count[v] count[v] cumu cumu+=current // 4: Make the result for i in 0 until A.length: result[count[a[i]]] A[i] count[a[i]] +=1 0 1 2 3 4 5 6 7 8 9 10 11 2 result Kohta 4, ensimmäinen iteraatio Kopioidaan alkio e 0 syötetaulukon kohdasta 0 tulostaulukon result kohtaan index[e 0 ] Lisätään arvoa index[e 0 ] yhdellä 71/80

Esimerkki: Oktaalijärjestelmän numeroita sisältävän taulukon järjestäminen original counting-sort(a): 0 1 2 3 4 5 6 7 8 9 10 11 2 3 2 0 7 7 2 3 1 4 6 0 // 1: Allocate and init arrays count int-array of length k 0 1 2 3 4 5 6 7 result int-array of length A.length count 2 1 3 2 1 0 1 2 0 1 2 3 4 5 6 7 for v in 0 until k: count[v] 0 index 0 2 4 6 8 9 9 10 // 2: Count occurrences for i in 0 until A.length: count[a[i]] +=1 // 3: Cumulative occurrences cumu 0 for v in 0 until k: current count[v] count[v] cumu cumu+=current // 4: Make the result for i in 0 until A.length: result[count[a[i]]] A[i] count[a[i]] +=1 0 1 2 3 4 5 2 result 6 7 8 9 10 11 3 Kohta 4, toinen iteraatio Kopioidaan alkio e 1 syötetaulukon kohdasta 1 tulostaulukon result kohtaan index[e 1 ] Lisätään arvoa index[e 1 ] yhdellä 72/80

Esimerkki: Oktaalijärjestelmän numeroita sisältävän taulukon järjestäminen original counting-sort(a): 0 1 2 3 4 5 6 7 8 9 10 11 2 3 2 0 7 7 2 3 1 4 6 0 // 1: Allocate and init arrays count int-array of length k 0 1 2 3 4 5 6 7 result int-array of length A.length count 2 1 3 2 1 0 1 2 0 1 2 3 4 5 6 7 for v in 0 until k: count[v] 0 index 0 2 4 7 8 9 9 10 // 2: Count occurrences for i in 0 until A.length: count[a[i]] +=1 // 3: Cumulative occurrences cumu 0 for v in 0 until k: current count[v] count[v] cumu cumu+=current // 4: Make the result for i in 0 until A.length: result[count[a[i]]] A[i] count[a[i]] +=1 0 1 2 3 2 4 5 2 result 6 7 8 9 10 11 3 Kohta 4, kolmas iteraatio Kopioidaan alkio e 2 syötetaulukon kohdasta 2 tulostaulukon result kohtaan index[e 2 ] Lisätään arvoa index[e 2 ] yhdellä 73/80

Esimerkki: Oktaalijärjestelmän numeroita sisältävän taulukon järjestäminen original counting-sort(a): 0 1 2 3 4 5 6 7 8 9 10 11 0 0 1 2 2 2 3 3 4 6 7 7 // 1: Allocate and init arrays count int-array of length k 0 1 2 3 4 5 6 7 result int-array of length A.length count 2 1 3 2 1 0 1 2 0 1 2 3 4 5 6 7 for v in 0 until k: count[v] 0 index 2 3 6 8 9 9 10 12 // 2: Count occurrences for i in 0 until A.length: count[a[i]] +=1 // 3: Cumulative occurrences cumu 0 for v in 0 until k: current count[v] count[v] cumu cumu+=current // 4: Make the result for i in 0 until A.length: result[count[a[i]]] A[i] count[a[i]] +=1 0 0 1 0 2 1 3 2 4 2 5 2 Kohta 4 6 3 result 7 3 8 4 9 6 Jatketaan, kunnes ollaan taulukon lopussa 10 7 Kopioidaan tulostaulukko result takaisin alkuperäiseen jos tahdotaan 11 7 74/80

Laskemisjärjestämisen ominaisuuksia: Vakaa Tarvitsee lisämuistia määrän Θ(k + n) Toimii ajassa Θ(k + n) Erittäin hyvä algoritmi jos k on pieni ja n k 75/80

Eniten merkitsevä numero ensin -kantalukujärjestäminen (engl. most-significant-digit-first radix sorting, MSD radix sorting) Oletetaan, että järjestettävät alkiot ovat sekvenssejä numeroita (engl. digit) joukosta 0,..., k 1 ja tahdotaan järjestää ne sanakirjajärjestykseen Esimerkiksi 32-bittiset kokonaisluvut ovat sekvenssejä, jotka koostuvat 4 kpl 8-bittisiä tavuja (numeroita välillä 0 255) ASCII-merkkijonot ovat sekvenssejä ASCII-merkkejä (numeroita välillä 0 127) Eniten merkitsevä numero ensin -kantalukujärjestäminen järjestää alkiot ensin ensimmäisen numeron mukaan käyttäen laskemisjärjestämistä ja sen jälkeen rekursiivisesti järjestää kunkin osataulukon, joka alkaa samalla numerolla, samalla tavalla mutta jättäen huomiotta eniten merkitsevän numeron 76/80

Ominaisuuksia: Vakaa Tarvitsee määrän Θ(k + n) lisämuistia Toimii ajassa O((k + n)d), missä d on tarkasteltavien alkioiden numeroesityksen maksimipituus 77/80

Vähiten merkitsevä numero ensin -kantalukujärjestäminen (engl. least-significant-digit-first radix sorting, LSD radix sorting) Oletetaan jälleen, että alkiot ovat sekvenssejä numeroita joukosta {0,..., k 1} ja ne tahdotaan järjestää leksikograafiseen järjestykseen Nyt oletetaan lisäksi, että kaikki alkiot ovat saman mittaisia koostuen d numerosta Alkiot voisivat siis olla esimerkiksi 32-bittisiä kokonaislukuja, jotka koostuvat 4 kpl 8-bittisiä tavuja tai samanpituisia ASCII-merkkijonoja kuten vaikkapa suomalaisia sosiaaliturvatunnuksia Vähiten merkitsevä numero ensin -kantalukujärjestäminen järjestää ensin alkiot viimeisimmän (vähiten merkitsevä) numeron mukaan käyttäen vakaata laskemisjärjestämistä ja sen jälkeen iteratiivisesti järjestää alkiot samalla lailla vakaalla laskemisjärjestämisellä toiseksi vähiten merkitsevän numeron mukaan jne 78/80

Ominaisuuksia: vakaa tarvitsee lisätilaa määrän Θ(k + n) toimii ajassa O((k + n)d) Esimerkki: 16-bittisten etumerkittömien lukujen järjestäminen vähiten merkitsevä numero ensin -kantalukujärjestämisellä 4-bittiä kerrallaan 0 0f22 0 0a80 0 0101 0 0101 0 0101 1 1e02 1 0f60 1 1e02 1 1122 1 0a80 2 0a80 2 0101 2 0f22 2 2222 2 0f22 3 0f60 3 0f22 3 1122 3 1232 3 0f60 4 1232 5 2343 6 0101 stable sort with the last digit as the key 4 1e02 5 1232 6 1122 stable sort with the second last digit as the key 4 2222 5 1232 6 2343 stable sort with the third last digit as the key 4 2343 5 0a80 6 1e02 stable sort with the fourth last digit as the key 4 0fff 5 1122 6 1232 7 0fff 7 2222 7 0f60 7 0f22 7 1e02 8 1122 8 2343 8 0a80 8 0f60 8 2222 9 2222 9 0fff 9 0fff 9 0fff 9 2343 79/80

Toteutus ja kokeellinen tarkastelu Ohjelmointitehtävänä 80/80