Egyesíthető prioritási sor Értékhalmaz: EPriSor = S E, E-n értelmezett a lineáris rendezési reláció. Műveletek: S,S 1,S 2 : EPriSor, x : E {Igaz} Letesit(S, ) {S = /0} {S = S} Megszuntet(S) {} {S = S} Uresit(S) {S = /0} {S = S} SorBa(S,x) {S = Pre(S) {x}} {S /0} SorBol(S,x) {x = min(pre(s)) Pre(S) = S {x}} {S = {a 1,...,a n }} Elemszam(S) {Elemszam = n} {S /0} Elso(S,x) {x = min(pre(s)) Pre(S) = S} {S /0} Torol(S) {S = Pre(S) \ {min(pre(s))}} {S 1 = S 1,S 2 = S 2 } Egyesit(S 1,S 2,S) {S = Pre(S 1 ) Pre(S 2 ) S 1 = /0 S 2 = /0}} Módosítható EPriSort vizsgálunk, amelyben használjuk a KulcsotCsokkent(S,x,k) műveletet, ami x kulcsát a k értékre csökkenti, ha k < kulcs(x), és az műveletet, amely az x elemet törli az egyesíthető prioritási sorból. Mindkét műveletnél feltesszük, hogy x-et egy rá mutató pointer adja meg. Binomiális fák A binomiális fákat a következő rekurzív definícióval adhatjuk meg. A B 0 fa egyetlen pontot tartalmaz. A B k fa (k 1) pedig két összekapcsolt B k 1 fából áll, az egyik fa gyökércsúcsa a másik fa gyökércsúcsának a legbaloldalibb gyereke lesz. Lemma B i -re a következő állítások teljesülnek i 1 esetén: 1. 2 i pontja van 2. magassága i 3. az j-edik szinten ( i j) pontja van 4. jobbról-balra a gyökér j-edik fia B j 1, j = 1,...,i Bizonyítás. Teljes indukcióval. Az indukciós lépés a 3. tulajdonság esetén a binomiális együtthatók ismert ( ) ( ) ( ) i 1 i 1 i + = j j 1 j összefüggése alapján adódik, a többi tulajdonság esetén nyilvánvaló. Binomiális kupac Definíció: Egy fát kupacnak nevezünk, ha minden pontra igaz, hogy a kulcsa nem nagyobb a fiai kulcsainak a minimumánál. Definíció: Egy S binomiális kupac binomiális fák olyan {B 1,...,B k } sorozata, amelyre teljesül: 1. Minden B i binomiális fa. 1
2. Minden B i fa kupac. 3. Nincs két azonos fokszámú fa a sorozatban. 4. A fák fokszám szerint növekvőek a sorozatban. A binomiális fák pontszámára vonatkozó lemma alapján: Következmény: Minden n pontú {F 1,...,F k } binomiális kupac esetén k log 2 n. Az egyes binomiális fákat elsőfiú testvér ábrázolással tároljuk. A binomiális fák gyökérelemeit egy láncban tároljuk, amelynek kezdőpontját Fej(S) adja meg, a láncban a rákövetkező elemre a testver mutató mutat. Elso(S,x) A gyökérlista elemein kell végigmenni és kiválasztani a minimálisat. Elso(S,x) x:=nil y:=fej(s) min:=inf while(y!=nil) if kulcs(y)<min then min:=kulcs(y) x:=y y:=testver(y) return x A futási idő a gyökérlista hosszával arányos, így legrosszabb esetben O(logn). Egyesit(S,Q,R) Az egyesítés során többször használjuk két B k 1 fa összekapcsolását, az alábbi eljárás az y gyökércsúcsú fát rakja a z gyökércsúcsú fa alá. BinKapcsol(y,z) apa(y):=z testver(y):=efiu(z) Efiu(z):=y fokszam(z):=fokszam(z)+1 Szintén használjuk a BinKupFesul(S,Q) eljárást, amely S-nek és Q-nak a gyökérlistáját fésüli össze, a fokszám szerint. Az Egyesit algoritmus elsőként összefésüli a két gyökérlistát, majd végigmegy a közös listán, és a BinKapcsol eljárás segítségével gondoskodik róla, hogy ne legyen olyan fokszám, amely fokszámhoz két binomiális fa is tartozik. Az alapgondolat az, hogy ha az adott fokszámmal kettő fa van, akkor összekapcsolja őket egy nagyobb fokszámúvá, ha pedig három van (ilyen eset két fa összekapcsolása után jöhet létre), akkor a másodikat és harmadikat kapcsolja össze. Az algoritmus futási ideje O(log(n), mivel az összefésülésnél csak egyszer kell végigmenni a két listán, majd az Egyesit eljárásban egyszer az összefésült listán. 2
Egyesit(S,P,Q) Letesit(Q: EPriSor(BinKupac)) fej(q):=binkupfesul(s,p) If fej(q):=nil then Return Q megeloz(x):=nil x:=fej(q) kovet(x):=testver(x) while(kovet(x)!=nil) if (fokszam(x)!=fokszam(kovet(x))or (tesver(kovet(x)!=nil) and fokszam(testver(kovet(x)))=fokszam(x)) then megeloz(x):=x x:=kovet(x) else if kulcs(x)<kulcs(kovet(x)) then testver(x):=testver(kovet(x)) BinKapcsol(kovet(x),x) else if megeloz(x)=nil then fej(q):=kovet(x) else testver(megeloz(x)):=kovet(x) BinKapcsol(x,kovet(x)) x:=kovet(x) kovet(x):=testver(x) Return(Q) Sorba(S,x) A binomiális kupacba úgy szúrunk be elemet, hogy létrehozunk egy az elemből álló kupacot majd egyesítjük a binomiális kupaccal. Letesit(S : EPriSor(BinKupac)) apa(x):=nil Efiu(x):=Nil Testver(x):=Nil fokszam(x):=0 Fej(S ):=x Egyesit(S,S,S) Return(S) Mivel a végrehajtás során az Egyesit művelet adja meg az időigényt, ezért az O(logn). SorBol(S,x) 1. S gyökérlistájában a minimális elemet megkeressük (mint az Elso(S,x) műveletben), elmentjük az x változóban és töröljük a gyökérlistából. 2. S fiainak a láncában az elemek sorrendjét fordítsuk meg, az így kapott binomiális kupac legyen S. 3. Hajtsuk végre az Egyesit(S,S,S) eljárást. Az algoritmus futási ideje O(log(n), mivel az 1. lépés konstans idejű, a 2.-ben egy O(log(n)) méretű listát fordítunk meg, a 3 lépésben két legfeljebb log 2 (n) méretű binomiális kupacot egyesítünk. A Torol(S) eljárás pontosan így működik, csak nem menti el a minimális elemet az x változóban. Kulcsot csökkent 3
Az algoritmus a kupac MaxKupacol eljárásához hasonló ötlet alapján cserékkel felfele mozgatja az elemet addig, amíg a kupac tulajdonság helyre nem áll. KulcsotCsokkent(S,x,k) if k>kulcs(x) Then write "hibas adat" return kulcs(x):=k y:=x z:=apa(x) while(z!=nil and kulcs(y)<kulcs(z)) Csere(kulcs(y),kulcs(z)) ha van további adat azt is cseréljük y:=z z:=apa(y) Mivel minden fa mélysége O(log(n)), ezért az eljárás futási ideje is O(log(n)). Az algoritmus átállítja a törlendő elem kulcsát -re majd a minimális elemet (amely az átállítást követően a törlendő elem kivágja). KulcsotCsokkent(S,x,-Inf) Torol(S) Az algoritmus futási ideje O(logn), mivel a felhasznált eljárásoké annyi. Fibonacci kupac Definíció: A Fibonacci-kupac fák egy olyan S = {F 1,...,F k,} sorozata, ahol minden F i fa kupac. A fák gyökérelemeit egy kétirányú körláncban tároljuk, a Fibonacci kupacot egy a minimális elemére mutató min(s) mutatóval adjuk meg. Az egyes fákat egy olyan adatszerkezetben tároljuk, ahol egy pont fiai kétirányú körláncban vannak eltárolva. Tehát egy fapont a Fibonacci kupacban a következő mutatókkal rendelkezik: apa, Efiu, bal, jobb. Továbbá hozzárendelünk a pontokhoz két további értéket fokszam(x) x fiainak számát adja meg, megjelol(x) egy boolean érték, ami akkor igaz, ha x már vesztette el fiát azóta, hogy a jelenlegi apja fia lett. Futási idők elemzése A Fibonacci kupac esetén jobb korlátokat kapunk az egyes műveletek futási idejére, de ezek az átlagolt, amortizált költségekre vonatkoznak. Az egyes műveletek amortizált költségelemzését a következő potenciálfüggvény segítségével hajtjuk végre Φ(S) = t(s) + 2m(S), ahol t(s) az S gyökérlistájában levő fák számát, m(s) pedig a megjelölt pontok számát jelöli. Továbbá n(s) adja meg a Fibonacci kupacban levő elemek számát. 4
Az amortizált elemzés során feltételezzük, hogy adott egy D(n) felső korlát az n pontot tartalmazó Fibonacci kupacban szereplő csúcsok maximális fokszámára. Igazolni fogjuk, hogy D(n) = O(log(n)). Egyesít(S,Q,R) Az Egyesít művelet során a gyökérlistákat tartalmazó két körláncot kell egyesíteni, majd a min(s) és min(q) pontok közül a kisebb kulcsot tartalmazó pontot választjuk az egyesített gyökérlista kijelölt minimum pontjának. A művelet költsége. A potenciál változása: Φ(R) (Φ(S) + Φ(Q)) = (t(r) + 2m(R)) ((t(s) + 2m(S)) + (t(r) + 2m(R))) = 0, hiszen t(r) = t(s) +t(q) és m(r) = m(s) + m(q). Ezért az amortizált költség a tényleges költséggel egyenlő, azaz O(1). SORBA(S,x) Képezzünk a beszúrandó x elemből egy egypontú fát, majd az ebből a fából álló Fibonacci kupacot egyesítsük S-el. A művelet költsége. A potenciál változása: ((t(s)+1)+2m(s)) (t(s)+2m(s)) = 1. A tényleges költség O(1), így az amortizált költség O(1) + 1 = O(1). Sorbol(S,x) Az algoritmus a minimum elem fiait átrakja a gyökérlistába, majd végrehajtja a KIEGYENLIT eljárást. Sorbol(S,x) z:=min(s) if z=nil then return z for z minden x fiára x-et tegyük S gyökérlistájába apa(x):=nil vegyük ki z-t S gyökérlistájából if z=jobb(z) /egy pont volt a fában then min(s):=nil else min(s):=jobb(z) Kiegyenlit(S) n(s):=n(s)-1; A Kiegyenlit használja a FibKupSzerk(S,y,x) eljárást, ami az y gyökerű fát berakja az x gyökerű fa alá. Továbbá felhasznál egy A tömböt, amelynek a mérete D(n) és amelyre A[i] az i fokszámú fát fogja tartalmazni. KIEGYENLIT(S) for i:=1 to D(n) A[i]:=Nil for S minden w fájára x:=w; d:=x.fokszam; while (A[d]!=Nil) y:=a[d] /x-el egyező fokszámú csúcs if x.kulcs > y.kulcs then Csere(x,y) KupacotSzerkeszt(H,y,x); A[d]:=Nil; d:=d+1; 5
A[d]:=x; min(s):=nil for i:=1 to D(n) if A[i]!=Nil then tegyük A[i]-t S gyökérlistájába if (min(s)=nil) or (kulcs(a[i])< kulcs(min(s))) then min(s):=a[i] KupacotSzerkeszt(S,y,x) vegyük ki y-t S-ből tegyük y-t x egy fiává és növeljük x fokszámát megjelol(x):=hamis; Művelet költsége: A SorBol művelet amortizált költsége O(D(n)). Az egyesített gyökérlista mérete legfeljebb D(n) + t(s) 1, így a tényleges költség O(D(n) + t(s)). A potenciál a minimális pont kivágása előtt t(s) + 2m(S), a KIEGYENLIT végrehajtása után D(n) + 1 + 2m(S). Tehát az amortizált költség O(D(n) +t(s)) + ((D(n) + 1) + 2m(S)) (t(s) + 2m(S)) = O(D(n)) + O(t(S)) t(s). Amely költség O(D(n)) nagyságrendű, ha a potenciálfüggvényt konstanszorosára növeljük úgy, hogy dominálja az O(t(S))-ben szereplő konstanst. Kulcsot csökkent Az algoritmus kivágja az adott elemet, ha sérül a kupactulajdonság, továbbá a KASZKÁD-VÁGÁS algoritmus segítségével gondoskodik arról, hogy ne legyen olyan pont, ami több fiát is elveszti. KulcsotCsokkent(S,x,k) if k>kulcs(x) Then write "hibas adat" return kulcs(x):=k y:=apa(x) if y!=nil and kulcs(x)<kulcs(y) Then KIVÁG(S,x,y) KASZKÁD-VÁGÁS(S,y) if kulcs(x)<kulcs(min(s)) Then min(s):=x KIVÁG(S,x,y) vegyük ki x-et y fiainak listájából tegyük bele x-et S gyökérlistájába apa(x):=nil megjelol(x):=hamis KASZKÁD-VÁGÁS(S,y) z:=apa(y) If z!=nil then if megjelol(y)=hamis then megjelol(y):=igaz else KIVÁG(S,y,z) KASZKÁD-VÁGÁS(S,z) 6
Amortizált költség: Legyen c azon csúcsok száma, amelyeket felhelyezünk a gyökérlistába. A KulcsotCsokkent eljárás tényleges költsége O(c). Vizsgáljuk a potenciál változását. Az új Fibonacci kupac gyökérlistájában c új csúcs szerepel, így a potenciálfüggvény első része c-vel nő, másrészt a KASZKÁD-VÁGÁSOK során c-1 megjelölt csúcs jelöletlenné válik, és egy jelöletlen jelölté, így a második rész 2(c 2)-vel csökken. Következésképpen a potenciál értéke c-4-el csökken. Így az amortizált költség konstans lesz, ha a potenciálfüggvényt konstansszorosára növeljük úgy, hogy dominálja az O(c) valós költségben szereplő konstanst. Az algoritmus átállítja a törlendő elem kulcsát -re majd a minimális elemet (amely az átállítást követően a törlendő elem kivágja). KulcsotCsokkent(S,x,-Inf) Torol(S) Az algoritmus futási ideje O(logn), mivel a felhasznált eljárásoké annyi. 7