8. Lajittelu, joukot ja valinta



Samankaltaiset tiedostot
8. Lajittelu, joukot ja valinta

Algoritmit 1. Luento 3 Ti Timo Männikkö

Algoritmit 1. Luento 12 Ke Timo Männikkö

Algoritmit 1. Luento 12 Ti Timo Männikkö

Algoritmit 1. Luento 11 Ti Timo Männikkö

1 Erilaisia tapoja järjestää

10. Painotetut graafit

811312A Tietorakenteet ja algoritmit III Lajittelualgoritmeista

811312A Tietorakenteet ja algoritmit, VI Algoritmien suunnitteluparadigmoja

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

Tietorakenteet ja algoritmit - syksy

Algoritmit 2. Luento 2 To Timo Männikkö

A TIETORAKENTEET JA ALGORITMIT

Algoritmit 2. Luento 2 Ke Timo Männikkö

Algoritmit 2. Luento 7 Ti Timo Männikkö

4 Tehokkuus ja algoritmien suunnittelu

Tiraka, yhteenveto tenttiinlukua varten

Algoritmit 1. Luento 10 Ke Timo Männikkö

Algoritmit 2. Luento 8 To Timo Männikkö

3. Hakupuut. B-puu on hakupuun laji, joka sopii mm. tietokantasovelluksiin, joissa rakenne on talletettu kiintolevylle eikä keskusmuistiin.

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

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

Algoritmit 2. Luento 3 Ti Timo Männikkö

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

A TIETORAKENTEET JA ALGORITMIT

Algoritmit 2. Luento 3 Ti Timo Männikkö

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

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

811312A Tietorakenteet ja algoritmit, , Harjoitus 7, ratkaisu

Algoritmit 1. Luento 10 Ke Timo Männikkö

Algoritmit 1. Luento 2 Ke Timo Männikkö

Algoritmi on periaatteellisella tasolla seuraava:

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

811312A Tietorakenteet ja algoritmit, , Harjoitus 3, Ratkaisu

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 2. Luento 14 Ke Timo Männikkö

6. Sanakirjat. 6. luku 298

3 Lajittelualgoritmeista

Hakupuut. tässä luvussa tarkastelemme puita tiedon tallennusrakenteina

Tutkimusmenetelmät-kurssi, s-2004

Luku 6. Dynaaminen ohjelmointi. 6.1 Funktion muisti

10. Painotetut graafit

1 Puu, Keko ja Prioriteettijono

Tehtävän V.1 ratkaisuehdotus Tietorakenteet, syksy 2003

Algoritmit 1. Luento 1 Ti Timo Männikkö

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

Ohjelmoinnin peruskurssi Y1

9 Erilaisia tapoja järjestää

Liitosesimerkki Tietokannan hallinta, kevät 2006, J.Li 1

Liitosesimerkki. Esim R1 R2 yhteinen attribuutti C. Vaihtoehdot

811312A Tietorakenteet ja algoritmit V Verkkojen algoritmeja Osa 2 : Kruskalin ja Dijkstran algoritmit

4. Joukkojen käsittely

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

5 Kertaluokkamerkinnät

v 1 v 2 v 3 v 4 d lapsisolmua d 1 avainta lapsen v i alipuun avaimet k i 1 ja k i k 0 =, k d = Sisäsolmuissa vähint. yksi avain vähint.

Algoritmit 1. Luento 8 Ke Timo Männikkö

Algoritmit 1. Luento 14 Ke Timo Männikkö

4. Sekvenssit Astetta soveltavat sekvenssit

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

Algoritmit 2. Luento 13 Ti Timo Männikkö

Listarakenne (ArrayList-luokka)

Kysymyksiä koko kurssista?

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

Algoritmit 2. Luento 6 To Timo Männikkö

Algoritmit 1. Luento 13 Ti Timo Männikkö

811312A Tietorakenteet ja algoritmit, , Harjoitus 4, Ratkaisu

2 Haku ja lajittelu. 2.1 Luvun hakeminen taulukosta X?? n-1. Haku ja lajittelu 35

Algoritmit 1. Luento 5 Ti Timo Männikkö

Ohjelmoinnin perusteet Y Python

Pinot, jonot, yleisemmin sekvenssit: kokoelma peräkkäisiä alkioita (lineaarinen järjestys) Yleisempi tilanne: alkioiden hierarkia

Algoritmit 1. Demot Timo Männikkö

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

4 Lajittelualgoritmeista

Algoritmit 2. Luento 5 Ti Timo Männikkö

Tietorakenteet, laskuharjoitus 7, ratkaisuja

Tietotyypit ja operaattorit

A TIETORAKENTEET JA ALGORITMIT

Graafit ja verkot. Joukko solmuja ja joukko järjestämättömiä solmupareja. eli haaroja. Joukko solmuja ja joukko järjestettyjä solmupareja eli kaaria

Luku 8. Aluekyselyt. 8.1 Summataulukko

Nopea kertolasku, Karatsuban algoritmi

4. Algoritmien tehokkuus

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

Luku 3. Listankäsittelyä. 3.1 Listat

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

Algoritmit 2. Demot Timo Männikkö

Tietorakenteet ja algoritmit syksy Laskuharjoitus 1

7. Tasapainoitetut hakupuut

TIE Tietorakenteet ja algoritmit 25

Imperatiivisen ohjelmoinnin peruskäsitteet. Meidän käyttämän pseudokielen lauseiden syntaksi

etunimi, sukunimi ja opiskelijanumero ja näillä

TKT20001 Tietorakenteet ja algoritmit Erilliskoe , malliratkaisut (Jyrki Kivinen)

Koe ma 1.3 klo salissa A111, koeaika kuten tavallista 2h 30min

Algoritmit 2. Luento 4 To Timo Männikkö

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.

12. Algoritminsuunnittelun perusmenetelmiä

12. Algoritminsuunnittelun perusmenetelmiä

SQL-perusteet, SELECT-, INSERT-, CREATE-lauseet

ALGORITMIT 1 DEMOVASTAUKSET KEVÄT 2012

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

Algoritmianalyysin perusteet

Transkriptio:

8. Lajittelu, joukot ja valinta Yksi tietojenkäsittelyn klassisista tehtävistä on lajittelu (järjestäminen) (sorting) jo mekaanisten tietojenkäsittelylaitteiden ajalta. Lajiteltua tietoa tarvitaan lukemattomissa yhteyksissä, esim. puhelinluetteloista binäärihakualgoritmiin (luku 6.2.), joka toimii oikein ainoastaan järjestetylle sekvenssille. Lajitteluongelma määritellään seuraavasti. Olkoon S n alkion sekvenssi, jossa alkiot ovat vertailukelpoisia keskenään täydellisen järjestyksen mielessä, ts. aina on mahdollista verrata kahta alkiota sen suhteen, kumpi niistä on pienempi ja kumpi suurempi. Lajitellaan ne kasvavaan järjestykseen tai ei vähenevään, jos sekvenssissä on yhtäsuuria alkioita. Edellä on esitetty yksinkertaiset ( naivit ) lajittelualgoritmit, kupla, lisäys ja valintalajittelu, jotka toimivat ajassa O(n 2 ) alkioiden määrään n nähden. Luvussa 5 käsiteltiin prioriteettijonoon perustuvaa lajittelua, joka toimi sekvenssitoteutuksella ajassa O(n 2 ), mutta kekolajitteluna ajassa O(n log n). Tässä luvussa esitetään tehokkaat lomituslajittelu ja pikalajittelu sekä tarkastellaan lyhyesti lokerolajittelua. Lisäksi esitetään tietorakenne joukko ja lajittelusta tavallaan johdettu valinta. Oletetaan, että kahden alkion välinen vertailu on aina tehtävissä ajassa O(1). 8.1. Lomituslajittelu Esitetään lomituslajittelu (merge sort), joka on luontevaa toteuttaa rekursiivisena. 8. luku 394 8. luku 395 Lomituslajittelu perustuu yleiseen algoritmien suunnittelumenetelmään nimeltä hajota ja hallitse (divide and conquer). Tämä käsittää kolme vaihetta: 1. Hajota: Jos syötteen koko on pienempi kuin ennalta määrätty kynnysarvo, kuten yksi tai kaksi alkiota, ongelma ratkaistaan (triviaalina) suoraviivaisella menetelmällä ja palautetaan saatu ratkaisu. Muuten jaetaan syöte kahteen tai useampaan erilliseen osajoukkoon. 2. Rekursio: Ratkaistaan rekursiivisesti osaongelmat osajoukkoihin liitttyen. 3. Hallitse: Osaongelmien ratkaisut lomitetaan alkuperäisen ongelman ratkaisuun. 8. luku 396 Edeltävä menettely oli esitetty yleisessä muodossa. Esitetään se nyt erityisesti lajittelua varten, missä on sekvenssi S ja n alkiota: 1. Hajota: Jos sekvenssissä S on ainakin kaksi alkiota (mitään ei tarvitse tehdä, jos on yksi tai ei yhtään), poistetaan kaikki alkiot sekvenssistä S ja sijoitetaan ne sekvensseihin ja, joista kumpikin sisältää noin puolet S:n alkioista, ts. käsittää ensimmäiset n/2 alkiota ja loput n/2 alkiota. 2. Rekursio: Lajitellaan rekursiivisesti sekvenssit ja. 3. Hallitse: Sijoitetaan alkiot takaisin sekvenssiin S lomittamalla lajitellut sekvenssit ja yhdeksi lajitelluksi sekvenssiksi. 8. luku 397

Lomituslajittelun suoritusta on hyvä havainnollistaa lomituslajittelupuun (merge sort tree) avulla (kuva 8.1.). Siinä jokainen solmu edustaa lomituslajittelun rekursiivista kutsua. Solmuun v liittyy sekvenssi S ja solmun lapsiin (rekursiiviset kutsut) osasekvenssit ja. Puun lehdet liittyvät yksittäisiin alkioihin vastaten algoritmin tilanteita, joissa ei enää tehdä rekursiivisia kutsuja. 85 24 63 85 24 63 Visualisointi auttaa algoritmin analyysin ymmärtämisessä. Kun syöte jaetaan suurin piirtein kahtia jokaisen rekursiivisen kutsun yhteydessä, puun korkeus on luokkaa log n (2 kantainen logaritmi). Lause 8.1. Lomituslajittelun suoritusta kuvaavan puun korkeus on syötejoukon kooltaan n tilanteessa log n. Perustelu voisi olla harjoituksena. Lausetta hyödynnetään lomituslajittelun suoritusajan laskemisessa. 8. luku 398 (a) Kuva 8.1. (alku) Lomituslajittelun havainnollistaminen puuna, jossa solmut vastaavat rekursiivista kutsua. Pisteviivalla merkityt solmut tarkoittavat kutsuja, joita ei ole vielä tehty. (b) 8. luku 399 63 63 63 63 85 24 24 85 24 85 85 24 (c) (d) (e) (f) Kuva 8.1. (jatkoa) Tyhjät solmut ohuella viivalla reunustettuna tarkoittavat tehtyjä kutsuja. Paksulla yhtenäisellä viivalla reunustettu solmu on nykyinen kutsu. 8. luku 400 Kuva 8.1. (jatkoa) Loput solmut eli ne, jotka on reunustettu ohuella yhtenäisellä viivalla ja eivät ole tyhjiä, edustavat kutsuja odottamassa lapsisolmun rekursion palautusta. 8. luku 401

63 63 63 85 24 63 (g) (h) (i) (j) Kuva 8.1. (jatkoa) Tilanne (h) on hallitse vaihe. Kuva 8.1. (jatkoa) 8. luku 402 8. luku 403 63 63 63 63 (k) (l) (m) (n) Kuva 8.1. (jatkoa) Kuva 8.1. (jatkoa) 8. luku 404 8. luku 405

24 63 85 63 24 63 85 63 (o) (p) (q) (r) Kuva 8.1. (jatkoa) Tilanne (o) on hallitse vaihe. Kuva 8.1. (jatkoa) Tilanne (q) on hallitse vaihe. 8. luku 406 8. luku 407 24 63 85 24 63 85 24 63 85 17 31 96 17 24 31 63 85 96 17 31 96 (s) (t) (u) (v) Kuva 8.1. (jatkoa) Tilanteiden (s) ja (t) välistä on jätetty useita muita esittämättä. Tilanne (t) on hallitse vaihe. Kuva 8.1. (loppu) Tilanne (v) on hallitse vaihe. 8. luku 408 8. luku 409

Mietitään nyt tämän hajota ja hallitse algoritmin vaiheita yksityiskohtaisesti. Lomituslajittelun hajota ja rekursiovaiheet ovat yksinkertaisia. Sekvenssin kooltaan n hajottaminen jakaa sen asteesta n/2 1 lukien kahdeksi osasekvenssiksi. Rekursiivinen kutsu käsittää vain näiden sekvenssien välityksen parametreina. Laskennallisesti eniten vaativa on hallitse vaihe, joka lomittaa lajitellut osasekvenssit yhdeksi lajitelluksi. Koodina 8.1. esitetty algoritmi merge (lomittaa) poistaa iteratiivisesti pienemmän alkion osasekvensseistä ja lisää sen tulossekvenssin loppuun, kunnes osasekvenssit tyhjenevät. Kuvassa 8.2. on esimerkki lomituksesta. Algorithm merge(,, S): Input: Sekvenssit ja (alkioiden välillä määritelty täydellinen järjestys) lajiteltuna ei vähenevässä järjestyksessä ja tyhjä sekvenssi S. Output: Sekvenssi S, joka sisältää sekvenssien ja alkioiden unionin lajiteltuna ei vähenevään järjestykseen. Sekvenssit ja tyhjenevät suorituksen aikana. while ei ole tyhjä and ei ole tyhjä do if.first().element().first().element() then {siirrä :n ensimmäinen alkio sekvenssin S loppuun} S.insertLast(.remove(.first())) else {siirrä :n ensimmäinen alkio sekvenssin S loppuun} S.insertLast(.remove(.first())) Koodi 8.1. (alku) Lomitusalgoritmi lomittaa kaksi lajiteltua sekvenssiä yhdeksi lajitelluksi sekvenssiksi. 8. luku 410 8. luku 411 24 63 85 24 63 85 17 31 96 31 96 {siirrä :n loput alkiot sekvenssin S loppuun} while ei ole tyhjä do S.insertLast(.remove(.first())) {siirrä :n loput alkiot sekvenssin S loppuun} while ei ole tyhjä do S.insertLast(.remove(.first())) Koodi 8.1. (loppu) Lomitusalgoritmi lomittaa kaksi lajiteltua sekvenssiä yhdeksi lajitelluksi sekvenssiksi. 7 (a) (b) 63 85 31 96 63 85 7 24 7 24 31 (c) (d) Kuva 8.2. (alku) Esimerkki koodin 8.1. lomitusalgoritmin suorituksesta. 96 8. luku 412 8. luku 413

63 85 96 63 85 7 24 31 7 24 31 (e) (f) 85 96 7 24 31 63 7 24 31 63 85 (g) (h) 96 96 Tehdyt sekvenssien lisäykset ja poistot toimivat ajassa O(1) (kaksoislinkitetty lista tai rengasrakenne luvusta 4). Kun osasekvenssien alkioiden määrät ovat n 1 ja n 2, niin lomitusalgoritmin kolmen while silmukan iteraatioiden määrä on yhteensä n 1 + n 2, mistä tulee suoritusajaksi O(n 1 +n 2 ). Ilman yksityiskohtaista lomituslajittelun analyysia lopputulos on ymmärrettävissä seuraavasti. Hajota vaiheen suoritus on lineaarisessa suhteessa sekvenssin kokoon. Yllä osoitettiin samoin olevan hallitsevaiheen lomituksen. Kun i viittaa solmun v syvyyteen, solmussa käytetty aika on O(n/2 i ), koska solmuun v liittyvän rekursiivisen kutsun sekvenssin koko on n/2 i. Kuva 8.2. (loppu) Esimerkki lomitusalgoritmin suorituksesta. S 17 24 31 63 85 96 (i) 8. luku 414 Lomituslajittelupuuta edeltä tarkasteltaessa kokonaisuutena havaitaan siinä syvyydellä i olevan 2 i solmua ko. tasolla. Tästä saadaan kaikissa solmuissa tasolla i käytettävä kokonaisaika O(2 i n/2 i ), joka on O(n). Lauseen 8.1. mukaan puun korkeus on log n. Kun puun jokaisella log n + 1 tasolla käytetty aika on O(n), saadaan seuraava lause. 8. luku 415 Lause 8.2. Lomituslajittelualgoritmi lajittelee sekvenssin kooltaan n alkiota ajassa O(n log n). Lomituslajittelu toimii asymptoottisessa mielessä samassa ajassa kuin kekolajittelu. 8.2. Joukot Joukko (set) tarkoittaa vastaavaa matemaattista käsitettä, jolle määritellään myös vastaavat operaatiot, kuten leikkaus ja unioni. Joukko operaatiot eivät edellytä täydellisen järjestyksen relaatiota alkioiden välillä, mutta järjestysinformaatiota voidaan silti hyödyntää joukon toteutuksessa. 8. luku 416 Joukon abstrakti tietotyyppi ja yksinkertainen toteutus Joukko operaatiot ovat osittain samanlaisia kuin sanakirjan (luku 7). Perusjoukko operaatiot ovat unioni (union), leikkaus (intersection) ja erotus (subtraction), jotka määritellään oheisina joukkojen A ja B avulla: A B = {x: x A tai x B}, A B = {x: x A ja x B} ja A B = {x: x A ja x B}. Joukon A metodit ovat seuraavat: size(): Palauttaa joukon A alkioiden määrän. Tulos: kokonaisluku 8. luku 417

isempty(): Palauttaa totuusarvon sen mukaan, onko A tyhjä. Tulos: totuusarvo intersect(b): Palauttaa leikkauksen A B. Syöte: joukko Tulos: joukko insertelement(e): Lisää alkion e joukkoon A, jollei se ollut siellä ennestään. Syöte: alkio elements(): Palauttaa alkioiden luettelon joukosta A. Tulos: alkioiden luettelo ismember(e): Määrää, onko e joukossa A. Syöte: alkio Tulos: totuusarvo union(b): Palauttaa unionin A B. Syöte: joukko Tulos: joukko 8. luku 418 isequal(b): Palauttaa arvon tosi, jos ja vain jos on A = B. Syöte: joukko Tulos: totuusarvo Operaatiot unioni, leikkaus ja erotus määriteltiin niin, että ne ovat eituhoavia (nondestructive), ts. ne eivät muuta mainittujen joukkojen sisältöä (tulos talletetaan johonkin kolmanteen joukkoon). Tuhoavina ne voidaan yhtä hyvin määritellä, jolloin tulos asetetaan toiseen niistä ja toinen merkitään tyhjäksi. Eräs yksinkertaisimmista keinoista toteuttaa joukot on tallettaa niiden alkiot järjestettyyn sekvenssiin. Vaikka alkioiden välillä ei olisi täydellistä järjestystä, usein voidaan jonkinlainen järjestysrelaatio määritellä (jos ei muuta ole, niin voidaan käyttää ainakin alkioiden talletusosoitteita muistissa). Metodit size(), isempty() ja elements() saadaan suoraan sekvenssin avulla. 8. luku 419 Metodi insertelement(e) voidaan toteuttaa myös melko suoraviivaisesti sanakirjan avulla. (Poisto on esitettävissä sekvenssin avulla niin ikään, vaikka sitä ei tässä ole eritelty.) Joukko operaatiot ja geneerinen lomitus Joukko operaatiot unioni, leikkaus ja erotus toteutetaan geneerisen lomituksen avulla, joka saa syötteenään kaksi lajiteltua sekvenssiä (syötejouko) ja rakentaa niistä tulosjoukkoa edustavan sekvenssin. Sekvenssien soveltama järjestys voi olla mikä tahansa yhtenäinen järjestys (siis täydellinen järjestys). Geneerinen lomitusalgoritmi on esitetty koodina 8.2. Algorithm genericmerge(a,b): Input: Lajitellut sekvenssit A ja B. Output: Lajiteltu sekvenssi C. {Sekvenssejä A ja B ei hävitetä.} Olkoon A sekvenssin A kopio. Olkoon B sekvenssin B kopio. while A ja B eivät ole tyhjiä do a A.first() b B.first() if a < b then firstisless(a,c) A.removeFirst() Koodin 8.2. algoritmi tutkii iteratiivisesti ja vertaa sekvenssien A ja B nykyisiä alkioita a ja b, onko a < b, a = b vai a > b. Vertailun perusteella se kopioi toisen tai ei kumpaakaan tulossekvenssiin C. 8. luku 420 Koodi 8.2. (alku) Geneerinen lomitusalgoritmi parametrisoituna metodeilla firstisless, bothareequal ja firstisgreater. 8. luku 421

else if a = b then bothareequal(a,b,c) A.removeFirst() B.removeFirst() else firstisgreater(b,c) B.removeFirst() while A ei ole tyhjä do a A.first() firstisless(a,c) A.removeFirst() while B ei ole tyhjä do b B.first() firstisless(b,c) B.removeFirst() Koodi 8.2. (loppu) Geneerinen lomitusalgoritmi. 8. luku 422 Vertailun perusteella tehtävä toiminta riippuu suoritettavasta operaatiosta, unioni, leikkaus tai erotus. Jos on kysymyksessä esimerkiksi unioni, kopioidaan pienempi alkioista a ja b ja jos ne ovat yhtä suuria, kopioidaan toinen (kumpi vain tässä tapauksessa). Täten kopioidaan jokainen eri alkio, mutta ei luoda kaksoiskappaleita (duplikaatteja). Koodissa 8.2. mainitut metodit firstisless, bothareequal ja firstisgreater määriteltäisiin (ei esitetä tässä) sopivasti riippuen kulloinkin suoritettavasta operaatiosta. Leikkauksessa kopioidaan luonnollisesti ne alkiot, jotka esiintyvät molemmissa joukoissa A ja B. Erotuksessa kopioidaaan puolestaan ne, jotka ovat toisessa, mutta eivät toisessa joukossa. Alkioiden vertailut vievät aikaa O(1). Geneerisen lomituksen kokonaissuoritusaika on näin ollen lineaarinen O(n A + n B ), missä n A ja n B viittavaat vastaavien joukkojen kokoihin. Taulukossa 8.1. ovat lueteltuina eri operaatioiden suoritusajat. 8. luku 423 Taulukko 8.1. Järjestettyinä sekvensseinä toteutettujen joukkojen operaatioiden suoritusajat. Lukumäärä n viittaa käsittelyyn liittyvän joukon tai joukkojen (yhteis)kokoon. metodi suoritusaika size, isempty O(1) insertelement O(n) elements, ismember O(n) union, intersect, subtract O(n) isequal O(n) 8.3. Pikalajittelu Esiteltävä lajittelumenetelmä on nimeltään pikalajittelu (quick sort), joka lomituslajittelun tapaan perustuu hajota ja hallitse menettelyyn. Se on jonkin verran erilainen, sillä laskenta suoritetaan pääosin ennen rekursiivisia kutsuja. Pikalajittelun korkean tason kuvaus Pikalajittelualgoritmi lajittelee sekvenssin S rekursiivisesti. S jaetaan osiin, jotka ovat toisiinsa nähden erillisiä, ja nämä lajitellut osasekvenssit yhdistetään. Algoritmi pitää sisällään kolme vaihetta: 8. luku 424 8. luku 425

1. Hajota: Jos sekvenssissä S on vähintään kaksi alkiota (mitään ei tarvitse tehdä, jos on tätä vähemmän), valitaan erityisalkio x sekvenssistä S. Se on pivotalkio. Olkoon x esim. viimeinen alkio. Poistetaan kaikki alkiot sekvenssistä S ja asetetaan ne kolmeen sekvenssiin: L: ne alkiot, jotka ovat pienempiä kuin x E: ne alkiot, jotka ovat yhtä suuria kuin x G: ne alkiot, jotka ovat suurempia kuin x Jos sekvenssin S alkiot ovat erisuuria, E sisältää luonnollisesti ainoastaan pivotalkion itsensä. 2. Rekursio: Lajitellaan rekursiivisesti sekvenssit L ja G. 3. Hallitse: Asetetaan alkiot takaisin sekvenssiin S järjestyksessä ensin sekvenssin L alkiot, sitten sekvenssin E alkiot ja lopuksi sekvenssin G alkiot. Kuten lomituslajittelussa, pikalajittelussakin suoritusta voidaan kuvata binäärisellä rekursiopuulla, kuvan 8.3. pikalajittelupuuna. Lomituslajittelusta eroten pikalajittelupuun korkeus on pahimmassa tapauksessa lineaarinen toki keskimääräisessä (käytännössä merkittävässä) logaritminen. Pahin tapaus esiintyy mm. n eri alkion ollessa valmiiksi järjestyksessä. Tällöin pivot on suurin alkio, jolloin sekvenssin L koko on n 1, kun taas sekvenssin E koko on yksi ja G nolla. Tämä tilanne toistuu joka kutsulla sekvenssin koon vähetessä yhdellä. Niinpä tämän erityisen tapauksen puun korkeus on n 1. 8. luku 426 8. luku 427 85 24 63 24 17 31 24 17 31 24 17 31 (a) Kuva 8.3. (alku) Pikalajittelun esimerkki, missä solmut edustavat rekursiivisia kutsuja. Pisteviivalla piirretyt solmut tarkoittavat kutsuja, joita ei vielä ole tehty. Vaihe (b) on jako. (b) 8. luku 428 (c) Kuva 8.3. (jatkoa) Paksulla reunaviivalla esitetty solmu edustaa suoritettavaa kutsua. Ohuella yhtenäisellä reunaviivalla esitetyt tyhjät solmut (ei vielä tässä) edustavat päätettyjä kutsuja. Muunlaiset solmut, kuin mainitut tyypit, edustavat odottavia. Vaihe (d) on jako. (d) 8. luku 429

31 31 31 31 24 17 17 24 17 24 17 24 (e) (f) (g) (h) Kuva 8.3. (jatkoa) Vaihe (f) on jako. Kuva 8.3. (jatkoa) 8. luku 430 8. luku 431 31 31 31 17 24 31 17 17 24 17 24 24 (i) (j) (k) (l) Kuva 8.3. (jatkoa) Kuva 8.3. (jatkoa) Osa (k) on hallitse vaihe. 8. luku 432 8. luku 433

17 24 31 17 24 31 17 24 31 17 24 31 (m) (n) (o) (p) Kuva 8.3. (jatkoa) Kuva 8.3. (jatkoa) Osa (o) on hallitse vaihe. Huomaa, että vaiheen (p) jälkeen useita on jätetty esityksestä pois ennen vaihetta (q). 8. luku 434 8. luku 435 17 24 31 63 85 96 17 24 31 63 85 96 Pikalajittelu paikallaan Toistaiseksi ei ole tarkasteltu lajittelumenetelmän tarvitsemaa muistitilaa itse lajiteltavan aineiston lisäksi. Lajittelualgoritmi toimii paikallaan (in place), jos se käyttää pelkästään vakiomäärän muistitilaa lajiteltavan aineiston lisäksi. Tällainen lajittelumenetelmä on varsin tehokas tilankäytön kannalta. (q) Kuva 8.3. (jatkoa) Osa (r) on hallitse vaihe. (r) Kuvattu lomituslajittelu ei ole po. tyyppiä, ja sen muuttaminen paikallaan toimivaksi on monimutkainen tehtävä. Yleisesti ottaen tämän ei tarvitse olla mutkikasta. Esim. luvun 5 kekolajittelun tapauksessa muunnos käy helposti, kun sovelletaan taulukkona toteutettua sekvenssiä kekoa varten. Myös pikalajittelu on toteutettavissa toimivaksi paikallaan. 8. luku 436 8. luku 437

Käytetään itse sekvenssiä sisältämään osasekvenssit. Syötesekvenssin sisältämiä alkioita vaihdetaan keskenään, joten erillisiä osasekvenssejä ei tarvitse luoda. Toimitaan kahden indeksin, l (vasen) ja r (oikea) avulla. Jakovaihe tehdään selaamalla näiden rajoittamaa osasekvenssiä samanaikaisesti sekä indeksistä l eteenpäin että indeksistä r taaksepäin. Alkioita vaihdetaan pareittain, jos ne ovat väärässä järjestyksessä (kuva 8.4.) Indeksien kohdatessa osasekvenssit L ja G ovat vastakkain tämän kohtauspaikan eri puolilla. Algoritmi käsittelee sitten rekursiivisesti nämä osasekvenssit. Algoritmin paikallaan käsittely vähentää sekä suoritusaikaa (aikayksiköissä mitattuna) että muistitilan tarvetta. (Teknisesti ajatellen tämä ei ole puhtaasti paikallaan toimiva, koska rekursion hallinta käyttää ylimääräistä tilaa, mutta algoritmi on toteutettavissa iteratiivisesti niin, että tätäkään ei tapahdu, kun pinoa ei käytetä. Tätä erityiskysymystä ei pohdita tässä.) 8. luku 438 l l l 85 24 63 (a) 85 24 63 (b) 31 24 63 17 85 96 Kuva 8.4. (alku) Jakovaihe pikalajittelussa paikallaan. Indeksi l selaa sekvenssiä vasemmalta oikealle ja indeksi r oikealta vasemmalle. Vaihto tehdään, kun l on suuremmassa alkiossa kuin pivot ja r on pienemmässä kuin pivot. Lopullinen vaihto pivotin kanssa päättää jakovaiheen. (c) r r r 8. luku 439 31 24 63 17 85 96 l r (d) 31 24 17 63 85 96 r l (f) 31 24 17 63 85 96 31 24 17 85 96 63 l (e) r r (g) l Kuva 8.4. (jatkoa) Kuva 8.4. (loppu) 8. luku 440 8. luku 441

Kuten tämän luvun 8.3 alkupuolella mainittiin, pikalajittelun suoritusaika on pahimmassa tapauksessa O(n 2 ), missä n on lajiteltavien lukumäärä, ja se esiintyy mm. aineiston ollessa jo alkutilanteessa kasvavassa järjestyksessä. Paradoksaalista kyllä, pikalajittelu toimii täten huonosti tilanteessa, jossa lajittelun pitäisi olla helppoa. Samasta syystä johtuen myös aineiston ollessa melkein lajiteltua (sisältää järjestyksessä olevia osajonoja ja periaatteessa vain muutamilla vaihdoilla saataisiin järjestykseen) pikalajittelu toimii heikosti. Tilastollisesti mieltäen kaikista mahdollisista satunnaisista lajiteltavien alkutilanteista em. tapaukset ovat harvinaisia. Näin ollen keskimääräinen suoritusaika on pikalajittelun kannalta huomattavasti tärkeämpi seikka. Keskimääräisen tapauksen (odotusarvon) analyysiin tarkemmin kajoamatta todetaan sen muistuttavan lomituslajittelun analyysia ja algoritmin olevan tehokas, jälleen O(n log n). Tämä saadaan aikaan, kun jakovaiheissa osasekvenssit L ja G ovat kutakuinkin yhtä suuria. Pivotalkio voidaan valita satunnaisesti (tasainen jakauma), joka tekee tästä satunnaistetusta pikalajittelusta hyvin keskimääräisessä suoritusajassa pysyvän. 8. luku 442 8.4. Vertailuperusteisen lajittelun alaraja Edellä nähtiin, miten tehokkaimmat esitetyistä lajittelumenetelmistä tuottivat suoritusajaksi pahimman tapauksen tai odotusarvon mukaisesti O(n log n). Tällöin herää luonnollinen kysymys, onko näille olemassa ylipäänsä (jollekin menetelmälle) mainittua parempaa tulosta. Seuraavaksi esitetään yhteenvedonomaisesti tulema, että mainittu kompleksisuus liittää tähän myös alarajan, ts. parempaa tulosta ei voida saavuttaa millään vertailuihin perustuvalla menetelmällä pahimman tapauksen tilanteessa. Tarkastelun perustaminen vain vertailujen laskentaan riittää, koska suoritettavien kahden alkion välisten vertailujen määrä vaikuttaa olennaisesti koko suoritusaikaan (esim. vertailujen tuloksena seuraavat vaihdot ovat suoraan vertailun tuloksesta riippuva, vakioajassa tehtävä toiminta). Näin voidaan pohtia hypoteettista menetelmää. 8. luku 443 Sivuutetaaan analyysin yksityiskohdat ja mielletään perättäisten vertailujen suorittaminen kulkemiseksi puussa T, jonka juuresta lähdetään liikkeelle, tehdään vertailuja ja päädytään lopulta lehteen. Jokainen lehti edustaa lajiteltavan aineiston S yhtä permutaatiota (permutation), kun oletetaan alkioiden olevan erillisiä (ei siis duplikaatteja). Puu T voidaan ymmärtää päätöspuuna, jossa vertailut tehdään. Saadaan seuraava lause. Lause 8.3. Minkä tahansa vertailuperusteisen lajittelualgoritmin pahimman tapauksen suoritusajan alaraja on Ω(n log n). Perustelu: Vertailuperusteisen algoritmin suoritusajan täytyy olla suhteessa päätöspuun T korkeuteen. Jokainen lajiteltavien alkioiden aineiston S permutaatio liittyy vain yhteen lehteen. Puussa T on ainakin n! = n(n 1)(n 2) 2 1 lehteä (yhtä kuin erilaisten permutaatioiden määrä), josta saadaan puun korkeudeksi vähintään log (n!). 8. luku 444 Kertomassa on ainakin n/2 tekijää, jotka ovat suurempia tai yhtä suuria kuin n/2. Tästä saadaan alaraja arvio: log (n!) log (n/2) n/2 = (n/2) log n/2, joka on Ω(n log n). Johtopäätöksenä on, että vertailuperusteista lajittelua ei voi tehdä pahimmalle tapaukselle yo. alarajaa nopeammin. Taas pulpahtaa mieleen kysymys, onko mahdollista tehdä muunlaisia lajittelualgoritmeja, jotka voivat asymptoottisessa mielessä kuitenkin toimia nopeammin kuin O(n log n). 8. luku 4

8.5. Lokerolajittelu On todella mahdollista saada asymptoottisesti nopeampia suoritusaikoja kuin O(n log n) rajoittamalla lajiteltavan sekvenssin alkioiden tyyppiä. Tällainen lähtökohta on täysin relevantti, koska vastaavia esiintyy monesti käytännön lajittelutilanteissa. Rajoitetaan lajiteltavien tietoyksiköiden (avain alkio pareja) avainten arvot kokonaisluvuiksi välille [0, N 1], missä N 2. Rajoitusta hyväksi käyttäen vertailujen tai niitä korvaavien toimintojen määrää avainten välillä voidaan merkittävästi supistaa. Perusmenetelmä on tällöin lokerolajittelu (bucket sort), joka ei perustu vertailuihin, vaan avainten soveltamiseen lokerotaulukon B indekseinä. Taulukon alkiot ovat indekseiltään välillä [0,N 1]. Tietoyksikkö avaimeltaan k sijoitetaan lokeroon B[k], joka on tämän avainarvon sekvenssi. 8. luku 446 Kun kaikki avaimet on asetettu lokeroidensa sekvensseihin, ne siirretään takaisin sekvenssiin S lajiteltuun järjestyksen lokeroitten sisältöjen mukaan B[0], B[1],, B[N 1]. Algoritmi esitetään koodina 8.3. Algorithm bucketsort(s): Input: Sekvenssi S, jossa on tietoyksiköt kokonaislukuavaimineen väliltä [0,N 1]. Output: Sekvenssi S lajiteltuna avainten ei vähenevään järjestykseen. Olkoon BNsekvenssin taulukko, jonka lokerot ovat aluksi tyhjiä. for jokaiselle sekvenssin S tietoyksikölle x do Olkoon k tietoyksikön x avain. Poistetaan x sekvenssistä S ja lisätään se sekvenssin B[k] loppuun. for i 0 to N 1 do for jokaiselle sekvenssin B[i] tietoyksikölle x do Poistetaan x lokerosta B[i] ja lisätään se sekvenssin S loppuun. Koodi 8.3. Lokerolajittelu. 8. luku 447 Suoraviivaisesti on todettavissa lokerolajitelun toimivan ajassa O(n + N) ja samoin tilassa O(n + N), missä n on lajiteltavien ja N lokeroiden määrä. Kun avainarvojen väli N on pieni verrattuna lajiteltavien määrään n, kuten N = O(n) tai N = O(n log n), niin lokerolajittelu on hyvin tehokas. Tehokkuus heikkenee koon N kasvaessa suhteessa lukumäärään n. Lajiteltaessa avain alkio pareja tärkeä kysymys esiintyy siinä, miten käsitellään sama avaimisia tietoyksiköitä, koska näiden alkiot kuitenkin tyypillisesti eroavat toisistaan. Jos mitkä tahansa tietoyksiköt (k i, e i ) ja (k j, e j ), jotka ovat tässä järjestyksessä ennen lajittelua, ovat aina lajittelun jälkeen samassa järjestyksessä, sanotaan algoritmin olevan stabiili (stable). Tätä ominaisuutta hyödyntäen lokerolajittelua voidaan muokata kokonaislukuja yleisempään kontekstiin, jolloin on kysymyksessä kantalukulajittelu (radix sort); tätä ei kuitenkaan tarkastella tässä kurssissa. 8. luku 448 8.6. Valinta On olemassa monenlaisia sovelluksia, joissa pitää hakea yksittäinen alkio alkiojoukosta. Tällaisia tilanteita ovat minimin ja maksimin haku, mutta myös muiden, erityisesti mediaanin (median) hakua tarvitaan usein (mediaani on alkio, jota pienempiä ovat puolet muista alkioista ja loput suurempia). Yleisesti esitetään kysymys järjestyksessä k:nnen alkion valinnasta (selection). Yksioikoinen lähestymistapa olisi lajitella aineisto ja sitten valita k:nneksi suurin. Tämä olisi tarkemmin ajatellen hyvin tehotonta, sillä nähdäänhän välittömästi, että esim. alkiot suuruusjärjestyksessä k=1, k=2 tai k=n 1 ovat saatavissa ajassa O(n). Täten herää mielenkiintoinen kysymys, onko mahdollista suorittaa valinta ajassa O(n) yleisestikin mille tahansa k:lle ja mediaanille, k= n/2, erityisesti. 8. luku 449

Valintaongelma on suoritettavissa ajassa O(n) mille tahansa k:lle. Tämä saadaan aikaan satunnaistetulla pikavalinnalla (randomized quickselect), joka toimii järjestämättömälle n alkion sekvenssille, kun täydellisen järjestyksen ominaisuus on voimassa kaikkien alkioparien välillä. Tämän menetelmän suoritusajan odotusarvo (keskimääräinen) on lineaarinen. Pahin tapaus on kaikesta huolimatta jälleen O(n 2 ). Aluksi on järjestämätön sekvenssi S, jossa on n alkiota, ja haettava avain k väliltä [1,n]. Pikavalinta algoritmi on ylätasolla nimensä mukaisesti pikalajittelun kanssa samankaltainen. Otetaan satunnaisesti alkio x sekvenssistä ja käytetään sitä pivotalkiona sekvenssin jakamiseksi osiin L, G ja E, joissa alkiot ovat pienempiä, suurempia tai yhtä suuria kuin pivotalkio. Avaimesta k riippuen päätetään, mistä noista kolmesta jatketaan rekursiota (koodi 8.4.). Algorithm quickselect(s,k): Input: Sekvenssi S, jossa on n alkiota, ja avain k väliltä [1,n]. Output: k:nneksi pienin alkio sekvenssistä S. if n = 1 then return sekvenssin S (ensimmäinen) alkio Otetaan satunnainen kokonaisluku r väliltä [0,n 1] Olkoon x sekvenssin S alkio astetta r. Poistetaan kaikki sekvenssin S alkiot ja asetetaan ne kolmeen osasekvenssiin: u L: ne, jotka ovat pienempiä kuin x u E: ne, jotka ovat yhtä suuria kuin x u G: ne, jotka ovat suurempia kuin x Koodi 8.4. (alku) Satunnaistettu pikavalinta algoritmi. 8. luku 4 8. luku 1 if k L then quickselect(l,k) else if k L + E then return x {jokainen alkio E:ssä on yhtä suuri kuin x} else quickselect(g,k L E ) {uusi valintaparametri!} Koodi 8.4. (loppu) Satunnaistettu pikavalinta algoritmi. 8. luku 2