Algoritmusok és adatszerkezetek II. Horváth Gyula Szegedi Tudományegyetem Természettudományi és Informatikai Kar horvath@inf.u-szeged.hu
3. Kiegyensúlyozott keresőfák A T tulajdonság magasság-egyensúlyozó tulajdonság, ha: 1. ( c R)( F)(T (F) h(f) c lg( F ) 2. T fenntartható Bővítés és Törlés során: Bovit, Torol után O(h(F)) időben helyreállítható. Következmény: F = n esetén Keres, Bovit, Torol : T lr (n) = O(lg n)
3.1. AVL-fák (Adelson-Velskij, Landis, 1962) Definíció. A P F pont (magasság-)egyensúlya: Egy(P) = h(job(p)) h(bal(p)) Definíció. Az F binfa AVL-fa, ha ( P F)( 1 Egy(P) 1) 3.1. tétel. Ha F AVL-fa, akkor h(f) 1.44 lg(n + 1); F = n Bizonyítás. Legyen N m az m magasságú, legkevesebb pontot tartalmazó AVL-fa pontjainak száma, azaz, N m F, ha F AVL-fa és h(f) = m N 0 = 0, N 1 = 1, N 2 = 2 N m = 1 + N m 2 + N m 1, ha m > 1 N m + 1 = (N m 2 + 1) + (N m 1 + 1) Jelölje B i := N i + 1 értéket. Tehát B 0 := 1,B 1 := 2,,B m = B m 2 + B m 1 (m > 1)? Φ m B m alsó korlátot keresünk. Tfh. 1 = Φ 0 B 0, Φ 1 B 1 és ha 2,,m 1-ig áll az B m = B m 2 + B m 1 Φ m 2 + Φ m 1 = Φ m 2 (1 + Φ) Ha (1 + Φ) Φ 2, akkor B m Φ m 2 (1 + Φ) Φ m 2 Φ 2 = Φ m De a Φ 2 = (1 + Φ) azaz (Φ 2 Φ 1 = 0) egyenlet megoldása: Φ = 1+ 5 2 1.618 Φ m B m = N m + 1 n + 1 m lgφ lg(n + 1) h(f) = m lgφ 1 lg(n + 1) = 1.44 lg(n + 1)
A Bovit és Torol műveletek megvalósítása: 1. közönséges bővítés/törlés; 2. AVL tulajdonság helyreállítása a keresőúton visszafelé haladva lokális forgatásokkal. Legyen U = P 0,P 1,,P m a gyökértől P m -ig vezető út az F bináris fában és X adatelem; F = P 0 P i+1 = Bal(P i ) P i+1 = Jobb(P i ). Definíció. Az U pontsorozat X-keresőút, ha 1. X < Adat(P i ) P i+1 = Bal(P i ) és X Adat(P i ) P i+1 = Jobb(P i )(i < m) 2. Vagy Adat(P m ) = X és ( i < m)(adat(p i ) X) vagy ( i m)(adat(p i ) X) és X < Adat(P m ) Bal(P m ) = és X Adat(P m ) Jobb(P m ) = Definíció. U X-bővítőút, ha 1. X < Adat(P i ) P i+1 = Bal(P i ) és X Adat(P i ) P i+1 = Jobb(P i )(i < m) 2. X < Adat(P m ) Bal(P m ) = és X Adat(P m ) Jobb(P m ) =
Definíció. U X-törlőút, ha 1. ( 0 t m)(adat(p t ) = X) és P 0,,P t X-keresőút 2. t = m és Bal(P t ) = Jobb(P t ) = vagy Bal(P t ) Jobb(P t ) és P t+1 = Jobb(P t ),P i+1 = Bal(P i )(t < i < m)
Tfh. P az X-bővítőút (X-törlőút) egy pontja és Bővítésnél: X Adat(P) és h(jobb(p)) nőtt Törlésnél: X < Adat(P)és h(bal(p)) csökkent. Bovit(P,x) a P Torol(Q,x) a P P α β α β 1. ábra. Fa magasságának változása bővítés és törlés hatására Művelet előtt Művelet után Egy(P) = 1 Egy(P) = 0, új h(p) = h(p) Egy(P) = 0 Egy(P) = 1, új h(p) = h(p) + 1 Egy(P) = +1 Egy(P) = 2, forgatni kell! új h(p) =?
a P BForgat(P) JForgat(Q) b Q b Q a P α γ β γ α β 2. ábra. Az egyszerű balra/jobbra forgatás megőrzi a keresőfa tulajdonságot. 3.2. Állítás. Az egyszerű balra és jobbra forgatás megőrzi a keresőfa tulajdonságot. Bizonyítás. Az F fa akkor és csak akkor keresőfa, ha az inoreder bejárása rendezett sorozatot ad. Az Inorder(F)=<F> jelöléssel: < F >=< α >,a,< β >,b,< γ > < F >=< α >,a,< β >,b,< γ >
3.2. A forgatás utáni egyensúly és magasság kiszámítása Tfh. az F fát bővítettük az x adattal. Legyen P az x-bővítőút egy pontja, és a bővítés P-nek a Q-gyökerű jobb-részfájába történt. Továbbá, Q magassága növekedett, és így Egy(P) = 2 lett. Ha a bővítés és AVL-egyensúly helyreállítás után Egy(Q) 0, akkor egyetlen balra forgatás helyreállítja az egyensúlyt. Hogyan változik a részfa magassága?
a P 2 α b 0 1 Q h + 2 h + 3 h β h + 1 h + 1 h h + 1 γ h + 2 h + 1 α a 1 0 P b 1 0 β h h + 1 h Q γ h + 1 h + 1 h + 3 h + 2 3. ábra. 1.a eset: AVL egyensúly helyreállítása egyszerű balra forgatással. Ha Egy(Q) = 1 a magasság csökken. Ha Egy(Q) = 0 nem csökken; csak törlés esetén lehet.
b 2 P h + 2 α a 1 0 Q β γ h h + 3 h + 1 h + 1 h h + 1 α h + 1 h + 1 a 0 1 Q b 0 1 P h + 1 h + 2 h + 2 h + 3 β γ h h + 1 h 4. ábra. 2.a eset: AVL egyensúly helyreállítása egyszerű jobbra forgatással. Ha Egy(Q) = 1 a magasság csökken. Ha Egy(Q) = 0 nem csökken; csak törlés esetén lehet.
Helyreállítás kettős balra forgatással Feltétel: Egy(P) = 2,Egy(Q) = 1
a P 2 α h b R 1 0 1 c 1 Q δ h h + 2 h + 3 β γ h h 1 h h 1 h h b R 0 a P 0 0 1 c +1 0 0 Q h + 1 h + 2 α β γ δ h h h 1 h h h h 1 h 5. ábra. 1.b eset: AVL egyensúly helyreállítása kettős balra forgatás.
U jegy(p) = Max(Egy(R), 0) U jegy(q) = Min(Egy(R), 0) U jegy(r) = 0 Egy(R) U jegy(p) U jegy(q) -1 0 1 0 0 0 1-1 0
Helyreállítás kettős jobbra forgatással Feltétel: Egy(P) = 2,Egy(Q) = +1
c P 2 a Q h + 2 α h +1 δ b R 1 h 0 1 β γ h h 1 h h 1 h h h + 3 b R 0 h + 1 a Q c 0 +1 0 0 1 0 P h + 1 h + 2 α β γ δ h h h 1 h h h h 1 h 6. ábra. 1.b eset: AVL egyensúly helyreállítása kettős jobbra forgatás.
U jegy(p) = Min(Egy(R), 0) U jegy(q) = Max(Egy(R), 0) U jegy(r) = 0 Egy(R) U jegy(p) U jegy(q) -1 1 0 0 0 0 1 0-1
Megvalósítás public class AVLFaPont<E extends Comparable<E>> extends BinKerFaPont<E>{ byte egy; public AVLFaPont(E x, AVLFaPont<E> b, AVLFaPont<E> j ){ super(x,b,j); public AVLFaPont(){ super(); public class BinKerFaAVL<E extends Comparable<E>> extends BinKerFa<E>{ public BinKerFaAVL(){ super();
public boolean Bovit(E x, boolean tobb){ AVLFaPont<E> p =(AVLFaPont<E>) gyoker; AVLFaPont<E> pp; int ken; if (p == null) { gyoker = new AVLFaPont<E>(x,null,null); return true; pp=p; while (p!= null) { ken = x.compareto(p.elem); pp=p; if (ken < 0) p=(avlfapont<e>)p.bal; else if (ken > 0) p=(avlfapont<e>)p.jobb; else {// = if (!tobb) return false; else{ p =(AVLFaPont<E>)p.jobb;
ken = x.compareto(pp.elem); if (ken<0){ pp.bal = new AVLFaPont<E>(x,null,null); pp.bal.apa=pp; Helyreallit(pp, false, 1); else{ pp.jobb = new AVLFaPont<E>(x,null,null); pp.jobb.apa=pp; Helyreallit(pp, true, 1); return true; //Bovit
public boolean Torol(E x){ AVLFaPont<E> p =(AVLFaPont<E>) gyoker; AVLFaPont<E> q; AVLFaPont<E> pp; int ken; while (p!=null && (ken = x.compareto(p.elem))!=0){ if (ken<0) p=(avlfapont<e>)p.bal; else p=(avlfapont<e>)p.jobb; if (p==null) return false; if (p.bal!= null && p.jobb!= null){ q=(avlfapont<e>)p.jobb; while (q.bal!=null){ q=(avlfapont<e>)q.bal; p.elem=q.elem; //helyettesítés a követővel p=q;
if (p.bal==null) q=(avlfapont<e>)p.jobb; else q=(avlfapont<e>)p.bal; pp=(avlfapont<e>)p.apa; if (q!=null) q.apa=pp; if (p==gyoker){ gyoker=q; else{ if (p==pp.bal){ pp.bal=q; Helyreallit(pp, false, -1); else{ pp.jobb=q; Helyreallit(pp, true, -1); return true;
// Az új egyensúly értékek táblázatai egyszerű balra forgatáskor; // Egy(Q) függvényében: private final byte[] BUjP={1,0; private final byte[] BUjQ={-1,0; // Az új egyensúly értékek táblázatai egyszerű jobbra forgatáskor; // Egy(Q) függvényében: private final byte[] JUjP={0,-1; private final byte[] JUjQ={0,1; // Az új egyensúly értékek táblázatai egyszerű kettős balra forgatáskor; // Egy(R) függvényében: private final byte[] KUjP={0,0,-1; private final byte[] KUjQ={1,0,0; private void Helyreallit(AVLFaPont<E> p, boolean jobbra, int nott){ //jobbra=true/false: bővítés/törlés a p jobb-részfájában //nott=1: bővítés, nott=-1: törlés AVLFaPont<E> orszem=new AVLFaPont<E>(); orszem.bal=gyoker; gyoker.apa=orszem; int pegy; AVLFaPont<E> pp, q, r;
while (p!=orszem){ pegy=p.egy; if (jobbra) p.egy+=nott; else p.egy-=nott; if (p.egy==0 && nott>0 pegy==0 && nott<0) break; pp=(avlfapont<e>)p.apa; jobbra=p==pp.jobb; if (p.egy==2){ q=(avlfapont<e>)p.jobb; if (q.egy>=0){ //egyszeres balra forgatás p.egy=bujp[q.egy]; // P Q q.egy=bujq[q.egy]; // / \ / \ p.jobb=q.bal; // a Q => P c q.bal=p; // / \ / \ q.apa=p.apa; p.apa=q; // b c a b if (p.jobb!=null) p.jobb.apa=p; if (p==pp.bal) pp.bal=q; else pp.jobb=q; p=q; if (q.egy==0 && nott>0 q.egy==-1 && nott<0) break;
else{ //kétszeres balra forgatás r=(avlfapont<e>)q.bal; // P R p.egy=kujp[r.egy+1]; // / \ / \ q.egy=kujq[r.egy+1]; // a Q => P Q q.bal=r.jobb; // / \ / \ / \ p.jobb=r.bal; // R d a b c d r.bal=p; // / \ r.jobb=q; // b c r.apa=p.apa; p.apa=r; q.apa=r; if (p.jobb!=null) p.jobb.apa=p; if (q.bal!=null) q.bal.apa=q; r.egy=0; if (p==pp.bal) pp.bal=r; else pp.jobb=r; p=r; if (nott>0)break;
else if (p.egy==-2){ q=(avlfapont<e>)p.bal; if (q.egy<=0){ //egyszeres jobbra forgatás p.egy=jujp[q.egy+1]; // P Q q.egy=jujq[q.egy+1]; // / \ / \ p.bal=q.jobb; // Q c => a P q.jobb=p; // / \ / \ q.apa=p.apa; p.apa=q; // a b b c if (p.bal!=null) p.bal.apa=p; if (p==pp.bal) pp.bal=q; else pp.jobb=q; p=q; if (q.egy==0 && nott>0 q.egy==+1 && nott<0 ) break;
else{//q.egy==1 //kétszeres jobbra forgatás r=(avlfapont<e>)q.jobb;// P R p.egy=kujq[r.egy+1]; // / \ / \ q.egy=kujp[r.egy+1]; // Q d => Q P p.bal=r.jobb; // / \ / \ / \ q.jobb=r.bal; // a R a b c d r.bal=q; r.jobb=p; // / \ r.apa=p.apa; // b c q.apa=r; p.apa=r; if (q.jobb!=null) q.jobb.apa=q; if (p.bal!=null) p.bal.apa=p; r.egy=0; if (p==pp.bal) pp.bal=r; else pp.jobb=r; p=r; if (nott>0) break; p=pp; //while gyoker=orszem.bal;
if (gyoker!=null) gyoker.apa=null; orszem=null;
3.3. A Sorozat adattípus megvalósítása AVL-fával Értékhalmaz: Sorozat = { a 1,...,a n : a i E Műveletek: S : Sorozat, x : E, i : Integer {Igaz Letesit(S) {S = {S = S Megszuntet(S) {Hamis {S = S Uresit(S) {S = {S = a 1,...,a n Elemszam(S) {Elemszam = n {S = a 1,...,a i,a i+1,...,a n 0 i n Bovit(S,i,x) {S = a 1,...,a i,x,a i+1,...,a n {S = a 1,...,a i 1,a i,a i+1,...,a n 1 i n Torol(S,i) {S = a 1,...,a i 1,a i+1,...,a n {S = a 1,...,a i,...,a n 1 i n Kiolvas(S,i,x) {x = a i S = Pre(S) {S = a 1,...,a i,...,a n 1 i n Modosit(S,i,x) {S = a 1,...,x,...,a n
Adatszerkezet választás. 1. Tároljuk az S = { a 1,...,a n sorozatot egy F bináris fában úgy, hogy F inorder bejárása az S sorozatot adja. a5 a2 a8 a1 a4 a6 a9 a3 a7 7. ábra. Az S = a 1,a 2,...,a 9 sorozat tárolása bináris fában inorder sorrendben.
2. Az F fa minden p pontjában tároljuk kiegészítő információként p bal-részfájában lévő pontok száma +1 értéket; rpoz(p) = 1+ F Bal(p). Tehát rpoz(p) a p pontra végrehajtott inorder bejárás során p sorszáma. A sorozat i-edik elemét tartalmazó pont keresése: a5 5 a2 2 a8 3 a1 1 a4 2 a6 1 a9 1 a3 1 a7 1 8. ábra. Kiegészítő információ: rpoz(p) = 1 + F Bal(p). private SFaPontAVL<E> Keres(int i){ SFaPontAVL<E> p=gyoker; int poz=i; while (poz!=p.rpoz) if (poz<p.rpoz) p=p.bal; else{ poz=poz-p.rpoz; p=p.jobb; return p;
3. A kiegészítő információ fenntartása bővítés és törlés során. Bővítés esetén ha a keresőút balra halad egy p ponttól, akkor p rpoz értékéhez egyet kell adni. Törlés esetén, ha a keresőút balra halad egy p ponttól, akkor p rpoz értékéből egyet le kell vonni. Továbbá, ha AVL-fát használunk, akkor az AVL-egyensúlyt helyreállító forgatás után aktualizálni kell a kiegészítő információt. P a r1 BFORGAT(P) Q b r1 + r2 Q b r2 P a r1 α γ β γ α β 9. ábra. Az rpoz kiegészítő információ változása egyszeres balra forgatás során.
P b r1 JFORGAT(P) Q a r2 Q a r2 P b r1 r2 γ α α β β γ 10. ábra. Az rpoz kiegészítő információ változása egyszeres jobbra forgatás során. P a r1 R b r1 + r3 Q c r2 P a r1 Q c r2 r3 α R b r3 δ α β γ δ β γ 11. ábra. Az rpoz kiegészítő információ változása kettős balra forgatás során.
P c r1 R b r2 + r3 Q a r2 R r3 b δ Q a r2 P c r1 r2 r3 α α β γ δ β γ 12. ábra. Az rpoz kiegészítő információ változása kettős jobbra forgatás során. AVL-fában a kiegészítő információ fenntartható a keresőút minden pontonjában konstans számú művelettel megvalósítható. Tehát ha a sorozat adattípust AVL-fával valósítjuk meg, akkor a KIOLVAS, MODOSIT, BOVIT, TOROL műveletek futási ideje legrosszabb esetben is a fa magasságával arányos, tehát O(lgn).
import java.util.*; public class SorozatAVL<E> implements Sorozat<E>{ public static class SFaPontAVL<E> extends BinFaPont<E>{ byte egy; int rpoz; SFaPontAVL(E x, int bpontsz, SFaPontAVL<E> b, SFaPontAVL<E> j){ super(x,b,j); this.rpoz=bpontsz; int eszam; SFaPontAVL<E> gyoker; SorozatAVL(){ eszam=0; gyoker=null;
private SFaPontAVL<E> Keres(int i){ SFaPontAVL<E> p=gyoker; int poz=i; while (poz!=p.rpoz) if (poz<p.rpoz) p=(sfapontavl<e>)p.bal; else{ poz=poz-p.rpoz; p=(sfapontavl<e>)p.jobb; return p;
public void Bovit(int i, E x){ if (i<0 i>eszam) throw new NoSuchElementException(); SFaPontAVL<E> p=gyoker; SFaPontAVL<E> apa=gyoker; int poz=i; boolean balra=true; if (gyoker==null){ eszam=1; gyoker = new SFaPontAVL<E>(x, 1, null, null); return;
while (p!=null){ apa=p; if (poz<p.rpoz){ p.rpoz++; p=(sfapontavl<e>)p.bal; balra=true; else{ poz=poz-p.rpoz; p=(sfapontavl<e>)p.jobb; balra=false; SFaPontAVL<E> ujp=new SFaPontAVL<E>(x, 1, null, null); ujp.apa=apa; if (balra){ apa.bal = ujp; Helyreallit(apa, false, 1); else{ apa.jobb = ujp; Helyreallit(apa, true, 1); ++eszam;
public void Torol(int i){ if (i<1 i>eszam) throw new NoSuchElementException(); int poz=i; SFaPontAVL< E> p = gyoker; SFaPontAVL< E> q; while (poz!=p.rpoz){ if (poz<p.rpoz){ --p.rpoz; p=(sfapontavl<e>)p.bal; else{ poz=poz-p.rpoz; p=(sfapontavl<e>)p.jobb;
if (p.bal!= null && p.jobb!= null){ q=(sfapontavl<e>)p.jobb; while (q.bal!=null){ --q.rpoz; q=(sfapontavl<e>)q.bal; p.elem=q.elem; p=q; if (p.bal==null) q=(sfapontavl<e>)p.jobb; else q=(sfapontavl<e>)p.bal; if (q!=null) q.apa=p.apa; if (p==gyoker){ gyoker=q; else{ if (p==p.apa.bal) p.apa.bal=q; else p.apa.jobb=q; --eszam;
public E Kiolvas(int i){ if (i<1 i>eszam) throw new NoSuchElementException(); SFaPontAVL<E> p=keres(i); return p.elem; public void Modosit(int i, E x){ if (i<1 i>eszam) throw new NoSuchElementException(); SFaPontAVL<E> p=keres(i); p.elem=x;
private int Epit(SFaPontAVL<E> p, E a[], int bal, int jobb){ int balm=0; int jobbm=0; SFaPontAVL<E> balf, jobbf; int kozep=(bal+jobb) >>1; p.elem=a[kozep]; p.rpoz=kozep-bal+1; if (bal<kozep){ balf=new SFaPontAVL<E>(); balm=epit(balf, a, bal, kozep-1); p.bal=balf; balf.apa=p; if (kozep<jobb){ jobbf=new SFaPontAVL<E>(); jobbm=epit(jobbf, a, kozep+1, jobb); p.jobb=jobbf; jobbf.apa=p; p.egy=(byte)(jobbm-balm); return 1+(balm<jobbm? jobbm : balm);
SorozatAVL(E a[]){ if (a.length==0){ this.gyoker=null; this.eszam=0; return; this.gyoker=new SFaPontAVL<E>(); this.eszam=a.length; Epit(gyoker, a, 0, a.length-1); private void tombbe(e[] a, int bal,int jobb, SFaPontAVL<E> p){ int k=bal+p.rpoz-1; a[k]=p.elem; if (bal<k) tombbe(a, bal, k-1, (SFaPontAVL<E>)p.bal); if (k<jobb) tombbe(a, k+1, jobb, (SFaPontAVL<E>)p.jobb); public E[] Tombosit(){ int n=eszam; E[] a=(e[]) new Object[n]; tombbe(a, 0, n-1, gyoker); return a;