8. Lajittelu, joukot ja valinta

Samankaltaiset tiedostot
8. Lajittelu, joukot ja valinta

Algoritmit 1. Luento 12 Ti Timo Männikkö

Algoritmit 1. Luento 12 Ke Timo Männikkö

Algoritmit 1. Luento 3 Ti Timo Männikkö

Algoritmit 1. Luento 11 Ti Timo Männikkö

Algoritmit 2. Luento 2 To Timo Männikkö

Algoritmit 2. Luento 2 Ke Timo Männikkö

Algoritmit 2. Luento 3 Ti Timo Männikkö

Algoritmit 2. Luento 3 Ti Timo Männikkö

811312A Tietorakenteet ja algoritmit III Lajittelualgoritmeista

Tietorakenteet ja algoritmit - syksy

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

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

Algoritmit 1. Luento 10 Ke Timo Männikkö

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

Algoritmit 2. Luento 7 Ti Timo Männikkö

10. Painotetut graafit

Algoritmit 2. Luento 8 To Timo Männikkö

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

4 Tehokkuus ja algoritmien suunnittelu

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

811312A Tietorakenteet ja algoritmit, , Harjoitus 7, ratkaisu

Algoritmit 2. Luento 14 Ke Timo Männikkö

811312A Tietorakenteet ja algoritmit, VI Algoritmien suunnitteluparadigmoja

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

Algoritmi on periaatteellisella tasolla seuraava:

1 Erilaisia tapoja järjestää

Hakupuut. tässä luvussa tarkastelemme puita tiedon tallennusrakenteina

Algoritmit 1. Luento 10 Ke Timo Männikkö

Algoritmit 1. Luento 5 Ti Timo Männikkö

Algoritmit 1. Luento 2 Ke Timo Männikkö

811312A Tietorakenteet ja algoritmit, , Harjoitus 3, Ratkaisu

Algoritmit 2. Luento 6 To Timo Männikkö

A TIETORAKENTEET JA ALGORITMIT

Algoritmit 1. Luento 1 Ti Timo Männikkö

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

Algoritmit 1. Luento 8 Ke Timo Männikkö

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

Algoritmit 1. Luento 14 Ke Timo Männikkö

Algoritmit 2. Luento 4 To Timo Männikkö

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.

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 13 Ti Timo Männikkö

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

5 Kertaluokkamerkinnät

3 Lajittelualgoritmeista

4. Sekvenssit Astetta soveltavat sekvenssit

811312A Tietorakenteet ja algoritmit , Harjoitus 2 ratkaisu

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

1 Puu, Keko ja Prioriteettijono

Algoritmit 1. Luento 7 Ti Timo Männikkö

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

9 Erilaisia tapoja järjestää

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

4. Joukkojen käsittely

Algoritmit 2. Luento 5 Ti Timo Männikkö

10. Painotetut graafit

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

Tietorakenteet, laskuharjoitus 3, ratkaisuja

Liitosesimerkki. Esim R1 R2 yhteinen attribuutti C. Vaihtoehdot

A TIETORAKENTEET JA ALGORITMIT

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

ALGORITMIT 1 DEMOVASTAUKSET KEVÄT 2012

Tiraka, yhteenveto tenttiinlukua varten

7. Tasapainoitetut hakupuut

TKT20001 Tietorakenteet ja algoritmit Erilliskoe , malliratkaisut (Jyrki Kivinen)

811312A Tietorakenteet ja algoritmit, , Harjoitus 5, Ratkaisu

A TIETORAKENTEET JA ALGORITMIT

811120P Diskreetit rakenteet

Algoritmit 2. Demot 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.

Tietorakenteet ja algoritmit

TIE Tietorakenteet ja algoritmit 25

Algoritmit 2. Luento 4 Ke Timo Männikkö

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

811120P Diskreetit rakenteet

811312A Tietorakenteet ja algoritmit, , Harjoitus 4, Ratkaisu

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

Toinen harjoitustyö. ASCII-grafiikkaa

Algoritmit 1. Luento 4 Ke Timo Männikkö

Tietorakenteet, laskuharjoitus 7, ratkaisuja

Mukautuvat järjestämisalgoritmit

Algoritmit 2. Luento 6 Ke Timo Männikkö

Ohjelmoinnin perusteet Y Python

Algoritmit 2. Luento 5 Ti Timo Männikkö

Tietorakenteet ja algoritmit syksy Laskuharjoitus 1

Ohjelmoinnin perusteet Y Python

Datatähti 2019 loppu

(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ä.

Algoritmit 1. Demot Timo Männikkö

Luku 8. Aluekyselyt. 8.1 Summataulukko

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

4. Algoritmien tehokkuus

Diskreetin matematiikan perusteet Laskuharjoitus 2 / vko 9

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

Harjoitustyön testaus. Juha Taina

Kysymyksiä koko kurssista?

Algoritmit 1. Luento 6 Ke Timo Männikkö

Kaksiloppuinen jono D on abstrakti tietotyyppi, jolla on ainakin seuraavat 4 perusmetodia... PushFront(x): lisää tietoalkion x jonon eteen

TAMPEREEN TEKNILLINEN YLIOPISTO

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. 8. luku 394

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 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 S 1 ja S 2, joista kumpikin sisältää noin puolet S:n alkioista, ts. S 1 käsittää ensimmäiset n/2 alkiota ja S 2 loput n/2 alkiota. 2. Rekursio: Lajitellaan rekursiivisesti sekvenssit S 1 ja S 2. 3. Hallitse: Sijoitetaan alkiot takaisin sekvenssiin S lomittamalla lajitellut sekvenssit S 1 ja S 2 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 S 1 ja S 2. Puun lehdet liittyvät yksittäisiin alkioihin vastaten algoritmin tilanteita, joissa ei enää tehdä rekursiivisia kutsuja. 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

85 24 63 45 17 31 96 50 17 31 96 50 85 24 63 45 (a) (b) Kuva 8.1. (alku) Lomituslajittelun havainnollistaminen puuna, jossa solmut vastaavat rekursiivista kutsua. Pisteviivalla merkityt solmut tarkoittavat kutsuja, joita ei ole vielä tehty. 8. luku 399

17 31 96 50 17 31 96 50 63 45 63 45 85 24 24 85 (c) (d) 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

17 31 96 50 17 31 96 50 63 45 63 45 85 24 85 24 (e) (f) 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

17 31 96 50 17 31 96 50 63 45 63 45 85 24 24 85 (g) (h) Kuva 8.1. (jatkoa) Tilanne (h) on hallitse vaihe. 8. luku 402

17 31 96 50 17 31 96 50 24 85 63 45 24 85 63 45 (i) (j) Kuva 8.1. (jatkoa) 8. luku 403

17 31 96 50 17 31 96 50 24 85 24 85 45 63 45 63 (k) (l) Kuva 8.1. (jatkoa) 8. luku 404

17 31 96 50 17 31 96 50 24 85 24 85 63 63 45 45 (m) (n) Kuva 8.1. (jatkoa) 8. luku 405

17 31 96 50 17 31 96 50 24 85 24 85 45 63 45 63 (o) (p) Kuva 8.1. (jatkoa) Tilanne (o) on hallitse vaihe. 8. luku 406

17 31 96 50 24 45 63 85 17 31 96 50 24 45 63 85 (q) (r) Kuva 8.1. (jatkoa) Tilanne (q) on hallitse vaihe. 8. luku 407

24 45 63 85 24 45 63 85 17 31 96 50 17 31 50 96 (s) (t) Kuva 8.1. (jatkoa) Tilanteiden (s) ja (t) välistä on jätetty useita muita esittämättä. Tilanne (t) on hallitse vaihe. 8. luku 408

24 45 63 85 17 31 50 96 17 24 31 45 50 63 85 96 (u) (v) Kuva 8.1. (loppu) Tilanne (v) on hallitse vaihe. 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. 8. luku 410

Algorithm merge(s 1, S 2, S): Input: Sekvenssit S 1 ja S 2 (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 S 1 ja S 2 alkioiden unionin lajiteltuna ei vähenevään järjestykseen. Sekvenssit S 1 ja S 2 tyhjenevät suorituksen aikana. while S 1 ei ole tyhjä and S 2 ei ole tyhjä do if S 1.first().element() S 2.first().element() then {siirrä S 1 :n ensimmäinen alkio sekvenssin S loppuun} S.insertLast(S 1.remove(S 1.first())) else {siirrä S 2 :n ensimmäinen alkio sekvenssin S loppuun} S.insertLast(S 2.remove(S 2.first())) Koodi 8.1. (alku) Lomitusalgoritmi lomittaa kaksi lajiteltua sekvenssiä yhdeksi lajitelluksi sekvenssiksi. 8. luku 411

{siirrä S 1 :n loput alkiot sekvenssin S loppuun} while S 1 ei ole tyhjä do S.insertLast(S 1.remove(S 1.first())) {siirrä S 2 :n loput alkiot sekvenssin S loppuun} while S 2 ei ole tyhjä do S.insertLast(S 2.remove(S 2.first())) Koodi 8.1. (loppu) Lomitusalgoritmi lomittaa kaksi lajiteltua sekvenssiä yhdeksi lajitelluksi sekvenssiksi. 8. luku 412

S 1 24 45 63 85 S 1 24 45 63 85 S 2 17 31 50 96 S 2 31 50 96 (a) S 17 (b) S 1 45 63 85 S 1 45 63 85 S 2 31 50 96 S 2 50 96 S 17 24 S 17 24 31 (c) (d) Kuva 8.2. (alku) Esimerkki koodin 8.1. lomitusalgoritmin suorituksesta. 8. luku 413

S 1 63 85 S 1 63 85 S 2 50 96 S 2 96 S 17 24 31 45 S 17 24 31 45 50 (e) (f) S 1 85 S 1 S 2 96 S 2 96 S 17 24 31 45 50 63 S 17 24 31 45 50 63 85 (g) (h) S 1 S 2 Kuva 8.2. (loppu) Esimerkki lomitusalgoritmin suorituksesta. S 17 24 31 45 50 63 85 96 (i) 8. luku 414

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. 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(): Tulos: totuusarvo Palauttaa totuusarvon sen mukaan, onko A tyhjä. 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

intersect(b): Palauttaa leikkauksen A B. Syöte: joukko Tulos: joukko 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. 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

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() 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. luku 424

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 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. 8. luku 426

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 427

85 24 63 45 17 31 96 50 24 45 17 31 50 85 63 96 (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

50 85 63 96 50 85 63 96 24 45 17 31 24 17 31 45 (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

50 85 63 96 50 85 63 96 31 45 31 45 24 17 17 24 (e) (f) Kuva 8.3. (jatkoa) Vaihe (f) on jako. 8. luku 430

50 85 63 96 50 85 63 96 31 45 31 45 17 24 17 24 (g) (h) Kuva 8.3. (jatkoa) 8. luku 431

50 85 63 96 50 85 63 96 31 45 31 45 17 17 24 24 (i) (j) Kuva 8.3. (jatkoa) 8. luku 432

50 85 63 96 50 85 63 96 31 45 17 24 31 45 17 24 (k) (l) Kuva 8.3. (jatkoa) Osa (k) on hallitse vaihe. 8. luku 433

50 85 63 96 50 85 63 96 17 24 31 17 24 31 45 45 (m) (n) Kuva 8.3. (jatkoa) 8. luku 434

50 85 63 96 17 24 31 45 50 85 63 96 17 24 31 45 (o) (p) 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 435

17 24 31 45 50 63 85 96 17 24 31 45 50 63 85 96 (q) (r) Kuva 8.3. (jatkoa) Osa (r) on hallitse vaihe. 8. luku 436

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. 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 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

85 24 63 45 17 31 96 50 l (a) r 85 24 63 45 17 31 96 50 l (b) r 31 24 63 45 17 85 96 50 l (c) r 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. 8. luku 439

31 24 63 45 17 85 96 50 l (d) r 31 24 17 45 63 85 96 50 l (e) r Kuva 8.4. (jatkoa) 8. luku 440

31 24 17 45 63 85 96 50 r (f) l 31 24 17 45 50 85 96 63 r (g) l Kuva 8.4. (loppu) 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 445

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.). 8. luku 450

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 451

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 452