4.3 Erillisten jouojen yhdisteet Ongelmana on pitää yllä ooelmaa S 1,..., S perusjouon X osajouoja, jota voivat muuttua ajan myötä. Rajoitusena on, että miään alio x ei saa uulua useampaan uin yhteen jouoon. Tässä Union-Find-ongelmassa sallittuja operaatioita ovat siis seuraavat: Mae-Set(x) : luo yhden alion jouon { x }, un x X. Operaatio saadaan tehdä vain erran ullein x. Find(x) : palauta edustaja siitä jouosta S johon x uuluu. Tämä edellyttää, että josus aiemmin on suoritettu Mae-Set(x). Edustaja on miä tahansa iinteä jouon S alio. Ainoa vaatimus on, että jos x S ja y S, niin Find(x) ja Find(y) palauttavat saman alion. Union(x, y) : Yhdistä alion x sisältävä ja alion y sisältävä jouo esenään. Edellyttää, että ummallein aliolle on josus tehty Mae-Set. Uuden jouon edustaja saa olla mielivaltainen sen alio, josin tyypilliset toteutuset valitsevat edustajasi joo alion Find(x) tai alion Find(y). 232
Esimeri perusjouo { a,..., }; jouojen (eräät mahdolliset) edustajat lihavoitu Mae-Set(a)... Mae-Set() { a } { b } { c } { d } { e } { f } { g } { h } { i } { j } { } Union(a, b) Union(c, d) Union(g, h) { a, b } { c, d } { e } { f } { g, h } { i } { j } { } Find(a) palauttaa a Find(b) palauttaa a Find(e) palauttaa e Union(b, d) Union(h, i) { a, b, c, d } { e } { f } { g, h, i } { j } { } Sovellusesimeri: Krusalin algoritmi Jouo muodostuu solmuista, jota ovat samassa puussa. Kaari (u, v) aiheuttaa sylin jos ja vain jos Find(u) = Find(v). Kaaren (u, v) lisääminen otetaan huomioon suorittamalla Union(u, v). 233
Jatossa n on perusjouon alioiden luumäärä. Rataisuyritys 1: jouot linitettyjä listoja. Union vaioajassa, mutta Find voi viedä Ω(n) Rataisuyritys 2: tauluoidaan ullein x sen sisältävän jouon edustaja. Find vaioajassa, mutta Union voi viedä Ω(n) Rataisu: linitetty metsä. Kuin jouo muodostaa puun, puun juuri jouon edustaja. c e f g j b d h a i { a, b, c, d } { e } { f } { g, h, i } { j } { } 234
Perustoteutus linitettynä metsänä: Mae-Set(x): p[x] := x Union(x, y): Lin(Find(x), Find(y)) Lin(x, y): p[x] := y Find(x): while p[x] x do x := p[x] return x Toteutus linitettynä metsänä ei vielä taaa tehouutta. Tehostamme operaatioita seuraavasti: Pidetään puut matalina äyttämällä luoaan (ran) perustuvaa tasapainotusta. Vältetään saman työn toistamista suorittamalla Find-operaation yhteydessä poluntiiivistys. 235
Puun luoan määräytyminen (perusidea): Ysisolmuisen puun juuren luoa on 0. Jos solmu y linitetään solmun x lapsesi, niin solmun x luoa muuttuu seuraavasti: Jos ran(x) ran(y) niin ran(x) := max { ran(x), ran(y) }. Jos ran(x) = ran(y) niin ran(x) := ran(x) + 1. Siis pienin puu, jona juuren luoa on, on binomipuu B. Jatossa esitettävä poluntiivistys sotee hieman tätä perusajatusta. Joa tapausessa Mae-Set ja Lin voidaan esittää muodossa Mae-Set(x): p[x] := x ran[x] := 0 Lin(x, y): if ran[x] > ran[y] then p[y] := x else p[x] := y if ran[x] = ran[y] then ran[y] := ran[y] + 1 236
Poluntiivistysessä samalla un operaatio Find(x) etsii polun solmusta x puun juuren, se oiaisee aiien polun varrelta löytyvien solmujen vanhempi-linit osoittamaan suoraan puun juureen. Find(x): r := x while p[r] r do r := p[r] q := r r := x s := p[x] while s r do p[r] := q r := s s := p[r] d c Find(a) b d a a b c 237
Huomataan ensin, että yleisyyttä rajoittamatta voidaan olettaa, että Union-operaatioiden sijaan äytetään vain Lin-operaatioita, jota saavat argumenttina osoittimen puun juureen. Tämä seuraa ysinertaisesti siitä, että operaatio Union(x, y) antaa saman tulosen uin operaatiot x := Find(x) y := Find(y) Lin(x, y ) Siis miä tahansa m Union-Find-operaation jono voidaan muuntaa oreintaan 3m operaation Lin-Find-jonosi. Tavoitteena on osoittaa, että m operaation jono äyttäen luoaan perustuvaa tasapainotusta ja poluntiivistystä vie oreintaan ajan O(mα(n)), missä α on erittäin hitaasti asvava ( äytännössä vaio, esim. α(n) 4 un n 10 500 ). Edellä esitetyn perusteella voidaan olettaa, että Union-operaation ohdistuvat puiden juuriin. Samoin selvästi riittää tarastella tapausta, että aii Mae-Set-operaatiot tehdään ennen mitään muita operaatioita. Sama asymptoottinen aiavaativuus voidaan saavuttaa myös muilla samantyyppisillä tasapainotus- ja polunlyhennysteniioilla. 238
Hitaasti asvava funtio α saadaan nopeasti asvavan funtion A äänteisfuntiona. Käytetään merintää A funtion A iteroinnille ertaa: A 0 (n) = n ja A +1 (n) = A(A (n)). Määritellään reursiivisesti { j + 1 jos = 0 A (j) = A j+1 1 (j) jos 1. Indesiä nimitetään funtion A tasosi. Selvästi A (j) asvaa aidosti seä tason että argumentin j suhteen. Pari ensimmäistä tasoa saadaan suljettuun muotoon helpolla indutiolla: A 1 (j) = 2j + 1 A 2 (j) = 2 j+1 (j + 1) 1 Lasetaan vielä parin tason ensimmäinen arvo: ja A 3 (1) = A 2 2 (1) = A 2(7) = 2047 A 4 (1) = A 2 3 (1) = A 3(2047) A 2 (2047) = 2 2048 2048 1 10 620. Siis un määritellään α(n) = min { A (1) n } saadaan α(n) 4 un n 10 620. Tätä rajaa voidaan verrata esim. arvioituun universumin atomien luumäärään 10 80. 239
Tasoitetussa analyysissa eseisesi tulee sen tarastelu, miten arvot ran[x] ja ran[p[x]] suhtautuvat toisiinsa. Luoa ran[x] voi vaihtua vain un x saa uuden lapsen Lin-operaatiossa. Sen sijaan p[x], ja näin ollen myön lauseeen ran[p[x]] arvo, voi muuttua myös suoritettaessa Find jona etsintäpolu ulee solmun x autta. Propositio A Kaiilla x pätee ran[x] ran[p[x]], ja yhtäsuuruus pätee vain jos x on puun juuri. Arvo ran[x] on alusi nolla, eiä osaan pienene. Sen jäleen, un x on linitetty toisen solmun lapsesi, arvo ran[x] ei myösään enää asva. Arvo ran[p[x]] ei osaan pienene. Todistus Suoraan operaatioiden toteutusesta. Propositio B Kaiilla x pätee ran[x] log n. Todistus Helppo indutio. 240
Ryhdymme nyt määrittelemään potentiaalia Φ. Potentiaali hetellä q (un on suoritettu ensimmäiset q operaatiota) on Φ q = x φ q (x) missä summa on aiien alioiden yli ja φ q (x) on aliolle x pian määriteltävä potentiaali hetellä q. Perusidea on, että φ q (x) on pieni, jos ran[p[x]] on hyvin paljon pienempi uin ran[x]. Tämä on seurausta siitä, että poluntiivistysten taia lini x p[x] oiaisee monen solmun yli. Jos jollain x operaatio Find(x) vie hyvin pitän ajan, niin solmusta x juureen johtavalla polulla oli paljon solmuja. Erityisesti polulla siis on ollut solmuja, joiden vanhempi-lini ei aiemmin oiaissut ovinaan paljon. Kun tehdään poluntiivistys, nämä solmut saavat uuden vanhemman ja niiden potentiaali putoaa. Potentiaaliin φ(x) vaiuttaa paitsi suoraan ran[x], myös ran[p[x]] epäsuorasti funtioiden level ja iter välitysellä. (Näissä pitäisi aiissa olla alaindesi q, osa ne vaihtuvat ajan myötä, mutta jätetään se selvyyden vuosi meritsemättä.) 241
Määritellään funtio level seuraavasti: level(x) = max { ran[p[x]] A (ran[x]) }. Siis un meritään ran[x] = r pätee level(x) = 0 jos ja muuten level(x) = jos r < ran[p[x]] 2r + 1, A (r) ran[p[x]] < A +1 (r) = A r+1 (r). Siis level(x) ertoo sellaisen, että funtiota A voidaan pisteestä r lähtien iteroida ainain erran mutta enintään r ertaa ennen uin mennään arvon ran[p[x]] yli. Ylläoleva aava osoittaa, että funtioiden A määritelmän nojalla tämä on ysiäsitteinen. Lisäsi on helppo nähdä 0 level(x) α(n) 1. Oloon edelleen ran[x] = r ja = level(x). Määritellään iter(x) = max { i ran[p[x]] A i (r) }. Siis iter antaa hienosäätöä sille, missä välillä [A (r), A r+1 (r)] arvo ran[p[x]] sijaitsee. Ylläesitetyn perusteella 1 iter(x) r. 242
Olemme nyt valmiit esittämään potentiaalin määritelmän. Kun ran, level ja iter aii viittaavat tilanteeseen hetellä q, asetetaan φ q (x) = α(n)ran[x] jos x juuri tai ran[x] = 0 φ q (x) = (α(n) level(x))ran[x] iter(x) muuten. Edellä esitetyistä rajoista ja 0 level(x) α(n) 1 1 iter(x) ran[r] seuraa suoraan ylä- ja alarajat 0 φ q (x) α(n)ran(x). Tarastellaan nyt potentiaalin muutosia. Lemma 1 Mae-Set-operaation tasoitettu aiavaativuus on O(1). Todistus Selvästi todellinen aiavaativuus on vaio, ja potentiaali ei muutu. 243
Tarastellaan nyt potentiaalin muutosia Union- ja Find-operaatioissa. Lemma 2 Oletetaan, että x ei ole juurisolmu. Missään Union- tai Find-operaatiossa solmun x potentiaali ei asva. Jos ran[x] > 0 ja level tai iter muuttuvat, solmun x potentiaali pienenee ainain yhdellä. Todistus Muulla uin juurisolmulla ran ei muutu. Jos ran[x] = 0, miään potentiaalin omponentti ei muutu. Tarastellaan siis tapausta ran[x] 1. Kosa ran[x] ei muutu, potentiaali muuttuu vain arvon ran[p[x]] muutosen taia. Oloon r = ran[x], = level(x) ja i = iter(x) jollain hetellä. Siis alusi A i (r) ran[p[x]] < Ai+1 (r). Kun arvoa ran[p[x]] ruvetaan asvattamaan, se voi ylittää rajat A i+1 (r), A i+2 (r), A i+3 (r),... ja vastaavasti iter(x) saada arvot i + 1, i + 2, i + 3,.... 244
Niin auan uin ran[p[x]] < A r+1 (r) = A +1 (r), taso level(x) ei muutu, joten joainen luvun iter(x) asvu pienentää suoraan potentiaalia φ(x). Jos ran[p[x]] lopulta saavuttaa rajan A r+1 (r) = A +1 (r), niin iter ei enää asva rajan r yli vaan x siirtyy tasolle + 1. Tason level(x) asvaminen yhdellä pienentää potentiaalia ran[x] verran. Samalla tosin iter(x) voi pienentyä, mutta osa muuttujan iter(x) arvoalue on { 1,..., ran(x) }, tästä aiheutuva potentiaalin asvu on enimmillään ran[x] 1 ysiöä. Siis tässäin muutosessa potentiaali oonaisuutena pienenee ainain yhdellä. Ysi operaatio voi tietysti muuttaa arvoa ran[p[x]] mielivaltaisen paljon, mutta muutos voidaan aina palautaa sarjasi yhden mittaisia aselia ja soveltaa joaiseen aseleeseen eriseen tätä päättelyä. 245
Olemme nyt valmiit varsinaiseen tasoitettuun analyysiin. Lemma 3 Kunin Lin-operaation tasoitettu aiavaativuus on O(α(n)). Todistus Symmetrian perusteella riittää tarastella operaatiota Lin(x, y) un y tulee solmun x vanhemmasi. Todellinen aiavaativuus on selvästi vaio. Pitää osoittaa, että potentiaalin mahdollinen asvu on O(α(n)). Lemman 2 nojalla potentiaali voi asvaa ainoastaan solmuissa x ja y. Solmu x muuttuu juuresta ei-juuresi ja sen ran ei muutu. Määritelmänsä muaan φ(x) siis joo pysyy arvossa 0 (tapaus ran[x] = 0) tai pienenee vähintään määrällä iter(x) 1. Joa tapausessa φ(x) ei ainaaan asva. Solmu y on juuri ennen ja jäleen operaation, ja ran[y] joo pysyy ennallaan tai asvaa yhdellä. Siis φ(y) joo pysyy ennallaan tai asvaa määrällä α(n). 246
Jäljellä on enää Find-operaatio, joa onin hanalin ja selittää potentiaalifuntion valinnan. Lemma 4 Kunin Find-operaation tasoitettu aiavaativuus on O(α(n)). Todistus Tarastellaan operaatiota Find(z). Oloon s solmun z etäisyys puunsa juuresta. Siis todellinen aiavaativuus on O(s). Osoitetaan, että potentiaali ei ainaaan asva, ja jos s α(n) + 2 niin potentiaali pienenee ainain määrällä s α(n) 2. Väite seuraa, un oletetaan potentiaalin vaioerroin sopivasti valitusi. Lemman 2 nojalla ainaaan muiden solmujen uin juuren potentiaali ei asva. Juuren ran ei muutu, joten sen potentiaaliaan ei muutu. Koonaispotentiaali ei siis ainaaan asva. Oloon nyt s α(n). Väitämme, että ainain s α(n) 2 solmun potentiaali aidosti pienenee. Tämä seuraa, un osoitamme, että solmun x potentiaali pienenee ainain, jos seuraavat ehdot ovat voimassa: 1. x on haupolulla solmun z ja juuren välissä (nämä solmut poisluien) ja 2. solmun x ja juuren välissä (taas nämä solmut poisluien) on ainain ysi solmu y jolla level(y) = level(x). 247
Oloot siis x ja y sellaiset haupolun solmut, että umpiaan ei ole polun päätepiste ja level[x] = level[y] = ennen poluntiivistystä. Meritään vielä i = iter(x). Tällöin seuraavat arviot pätevät: ran[p[x]] A i (ran[x]) ran[p[y]] A (ran[y]) ran[y] ran[p[x]] Kosa A on asvava, saadaan ennen poluntiivistystä ran[p[y]] A (ran[y]) A (ran[p[x]]) A (A i (ran[x])) Poluntiivistysen jäleen p[x] = p[y] ja siis ran[p[x]] = ran[p[y]]. Kosa poluntiivistysessä ran[x] ei muutu ja ran[p[y]] ei ainaaan pienene, poluntiivistysen jäleen saadaan ran[p[x]] A i+1 (ran[x])). Tiivistysessä siis solmulla x joo iter tai level asvaa, ja siis lemman 2 muaan potentiaali pienenee. Lemmoista 1, 3 ja 4 seuraa suoraan Korollaari Poluntiivistysellä ja tasapainotusella m Union-Find-operaatiota n-alioisessa perusjouossa voidaan suorittaa ajassa O(mα(n)). 248
Pienin yhteinen esivanhempi (Least Common Ancestor, LCA) Puun solmujen u ja v pienin yhteinen esivanhempi, LCA(u, v), on solmujen u ja v esivanhemmista se, joa on auimpana puun juuresta. Tarastellaan Union-Find-sovellusesimerinä LCA-ongelman offline-versiota: on annettu jouo P solmupareja, ja halutaan yhdellä puun läpiäynnillä määrätä LCA(u, v) aiille { u, v } P. Ongelma voidaan rataista värittämällä alusi aii puun solmut valoisisi ja utsumalla sitten LCA(r), missä r on puun juuri ja LCA seuraava reursiivinen proseduuri: LCA(u): 1. color[u] := Gray 2. Mae-Set(u) 3. ancestor[find(u)] := u 4. for aiille solmun u lapsille v do 5. LCA(v) 6. Union(u, v) 7. ancestor[find(u)] := u 8. color[u] := Blac 9. for aiilla v joilla { u, v } P do 10. print u, v, ancestor[find(v)] (Harmaa väri on vain analyysin selventämisesi, harmaat solmut voitaisiin jättää myös valeisi.) 249
Harmaat solmut muodostavat polun juuresta äsiteltävänä olevaan solmuun. Kussain jouossa on ysi harmaa solmu ja tämän solmun oomustat alipuut (joita voi tietysti olla useampiain, toisin uin alla olevassa aaviomaisessa uvassa). ancestor-find-ombinaatiolla uin musta solmu löytää oman harmaan solmunsa. Tämä harmaa solmu on selvästi oiea vastaus yseistä mustaa solmua oseviin LCA-yselyihin. (Todistusen ysityisohdat sivuutetaan.) 250