Dinamikus fák és utak Készítette: Kovács Péter
Dinamikus fák Különböző hálózati algoritmusokban gyakran van szükség dinamikusan változó fák kezelésére. Erre a feladatra bemutatunk egy hatékony adatszerkezetet. 2 Feladat: Tároljuk csúcsdiszjunkt gyökeres fák egy erdejét. (A fákra szerkezeti megkötés nincs.) Minden csúcshoz tároljunk egy valós költséget (cost). Valósítsuk meg minél hatékonyabban az alábbi műveleteket: maketree(v) parent(v), cost(v) findroot(v), findcost(v), addcost(v, x) link(v, w), cut(v) Cél: minél jobb amortizációs időt (≈ átlagos időt) elérni egy műveletsorozat végrehajtása során.
Dinamikus fa műveletek maketree(v): egy csúcsból álló fa létrehozása (v költsége 0 lesz) parent(v), cost(v): Egy csúcsra vonatkozó műveletek. A v csúcs szülőjének és költségének lekérdezése. 3 findroot(v) findroot(v), findcost(v), addcost(v): A v csúcstól a gyökérig vezető útra vonatkozó műveletek. findroot(v): a v csúcsot tartalmazó fa gyökércsúcsának megkeresése. findcost(v): az úton a minimális költségű csúcs megkeresése (ha több ilyen is van, akkor az utolsót, vagyis a legfelsőt kell megadni). addcost(v, x): az úton minden csúcs költségének növelése x-szel. v
Dinamikus fa műveletek Az eddigiek valójában statikus műveletek voltak. 4 link(v, w), cut(v): Dinamikus műveletek, amelyek megváltoztatják a fák szerkezetét. link(v, w): A v és w csúcsokat tartalmazó fák összekapcsolása. v mindig egy fa gyökere, amelyet w-hez kapcsolunk egy (v,w) él hozzáadásával. A fák éleit mindig fölfelé (a szülő felé) irányítottnak tekintjük. cut(v): A v csúcsot tartalmazó fa szétvágása két részfára a v-ből kivezető él törlésével. v w v
Dinamikus fák megvalósítása Egyszerű megvalósítás: Minden csúcshoz tároljuk a szülőjére mutató pointert és a költségét. maketree(v), parent(v), cost(v) műveletek:O(1) link(v, w), cut(v) műveletek:O(1) findroot(v), findcost(v), addcost(v, x) műveletek:O(n) (a v csúcs mélységével arányos futási idő, ami legrosszabb esetben O(n).) Cél: Ennél hatékonyabbat adni. Minden művelet (átlagos) ideje O(log n) legyen. Analógia: Prioritásos sor megvalósítása (pl. Dijkstra, Prim stb. algoritmusokhoz): rendezetlen/rendezett tömb (bináris) kupac Szótár megvalósítása (keresés, beszúrás, törlés műveletek): tömb/lista kiegyensúlyozott keresőfa (pl. AVL-fa) 5
Dinamikus fák megvalósítása Ötlet: A fákat implicit módon tároljuk. Minden fát szétbontunk csúcsdiszjunkt utakra és azokat összekötő élekre. Később bemutatunk egy olyan adatszerkezetet, amellyel a szükséges műveletek utakra hatékonyan elvégezhetők. Ezt felhasználva a faműveleteket útműveletekkel valósítjuk meg. 6
Fák felbontása utakra Az éleket két csoportba soroljuk: erős élek: gyenge élek: Egy csúcsba legfeljebb egy erős él vezethet. Ezzel a fákat csúcsdiszjunkt erős utakra particionáljuk. Lehetnek egy csúcsból álló erős utak is. Tárolás: Tároljuk az erős utakat (l. később). Minden úthoz egy rákövetkező (successor) csúcsot: ahova az út végpontjából kiinduló gyenge él vezet. Az aktuális felbontás a műveletek hatására folyamatosan változik! 7
Egy út kiemelése Szükségünk van még egy fontos műveletre. expose(v): A v csúcsból a gyökérbe vezető út kiemelése. Megváltoztatjuk az élek aktuális erős-gyenge felosztását oly módon, hogy ez egy erős út legyen. Az út minden éle erős él lesz, és v-be nem vezet erős él. 8 vv
Egy út kiemelése expose(v) megvalósítása: 9 function expose(node v): path; p := null; while v null w := successor(path_find(v)); (q,r) := path_split(v); if q null then successor(q) := v; p := path_concat(p,v,r); v := w; end while successor(p) := null; return p; end expose;
Műveletek megvalósítása maketree(v), parent(v), cost(v): a megfelelő faművelet meghívása (csak a v csúcsot érinti). findroot(v), findcost(v), addcost(v, x): Először expose(v): a v csúcstól a gyökérig vezető út kiemelése, a tárolás megváltoztatása. Utána a megfelelő faművelet végrehajtása. function findcost(node v): real; return path_findcost(expose(v)); end findcost; function findroot(node v): node; return path_findtail(expose(v)); end findroot; procedure addcost(node v, real x); path_addcost(expose(v)); end addcost; function findroot(node v): node; p := expose(v); return path_findtail(p); end findroot; 10
Műveletek megvalósítása link(v, w), cut(v): Hasonlóan: expose(v), utána a megfelelő faművelet végrehajtása, és a successor mutatók beállítása. 11 procedure link(node v, w); p := path_concat(null, expose(v), expose(w)); successor(p) := null; end link; procedure cut(node v); expose(v); (p,q) := path_split(v);// p üres lesz successor(v) := null; successor(q) := null; end link;
Elemzés A faműveletek meghatározó lépései: a különböző útműveletek; az expose(v) hívások, amelyek szintén útműveleteket hajtanak végre. Belátható, hogy m faművelet végrehajtása során: a path_concat és path_split műveletek száma O(m log n), a többi útművelet száma O(m). A következőkben pedig bemutatunk egy hatékony adatszerkezetet utak kezelésére, amellyel: az összes path_concat és path_split művelet megvalósítható O(m log n) időben, a többi útművelet pedig külön-külön O(log n) időben. Tehát: Az O(m) faművelet O(m log n) időben végrehajtható. Egy faművelet amortizált futási ideje O(log n). 12
Dinamikus utak A vizsgált adatstruktúra megvalósításához „dinamikus utak” hatékony kezelése szükséges. Műveletek: path_make(v): egyelemű út létrehozása a v csúcsból (0 költséggel). path_find(v): a v csúcsot tartalmazó út meghatározása. path_findtail(p): a p út végpontjának megkeresése. path_cost(v): a v csúcs költsége. path_findcost(p): a p úton a minimális költségű csúcs megkeresése (ha több ilyen is van, akkor az utolsót kell megadni). path_addcost(p,x): a p úton minden csúcs költségének növelése x-szel. path_concat(p,v,q): a p és q utak összekapcsolása (konkatenálása) a v csúcs közbeiktatásával. path_split(v): a v csúcsot tartalmazó út szétvágása a v-hez kapcsolódó élek törlésével. 13 p q v v
Dinamikus utak reprezentálása A továbbiakban dinamikus utak kezelésére adunk egy hatékony adatstruktúrát. Egy utat reprezentáljunk egy gyökeres bináris fával úgy, hogy: A fa csúcsai inorder sorrendben az út csúcsait adják. Egy belső csúcs egy részútnak feleltethető meg. A gyökércsúcs reprezentálja a teljes utat. 14 abcdefg a b c d e f g
Dinamikus utak reprezentálása Valamilyen módszerrel biztosítjuk a bináris fa kiegyensúlyozottságát. Így a műveleteket többségét el tudjuk végezni O(log n) időben. A nehézséget a költségek kezelése okozza. Szükséges műveletek: path_findcost(p): a minimális költségű csúcs megkeresése. path_addcost(p,x): minden csúcs költségének növelése x-szel. A triviális megvalósítással (minden csúcshoz tároljuk a költségét) ezek csak O(n) időben hajthatók végre. Ötlet: A költségeket implicit módon tároljuk. Az egyes csúcsokra és részfákra vonatkozó költségértékek különbségeit tároljuk csúcsonként. Így egy csúcshoz tartozó részfa (részút) minimális költsége megadható O(1) időben, a minimális költségű csúcs pedig O(log n) időben. 15
Költségek kezelése Jelölje cost(x) az x költségét, mincost(x) pedig az x-hez tartozó részfa csúcsai költségének minimumát. Minden x csúcshoz tároljuk a Δcost(x) és Δmin(x) értékeket: Δcost(x): az x költségének eltérése az x-hez tartozó részfa minimális költségétől. Δcost(x) = cost(x) – mincost(x) Δmin(x): az x-hez és a szülőjéhez tartozó részfa minimális költségének különbsége. Δmin(x) = mincost(x) – mincost(parent(x))ha x nem gyökér Δmin(x) = mincost(x)ha x gyökér Δcost(x) ≥ 0 minden x csúcsra, és Δmin(x) ≥ 0 minden nem gyökér x csúcsra. Ilyenkor mincost(x) meghatározható úgy, hogy a Δmin értékeket összeadjuk a bináris fa gyökerétől x-ig vezető úton. Ebből pedig cost(x) megkapható így: cost(x) = mincost(x) + Δcost(x). 16
Költségek kezelése Példa: 17 b (3) a (6)f (2) g (7)d (5) c (4) e (8) b (2,1) a (4,0)f (0,0) g (5,0)d (2,1) c (0,0)e (4,0) x ( cost(x) ) x ( Δmin(x), Δcost(x) )
Költségek kezelése path_findcost(p): A bemutatott implicit tárolással egy út (bináris fa) csúcsai költségeinek minimumát 1 lépésben meg tudjuk adni. Egy minimális költségű csúcsot pedig kereshetünk a következőképpen: Induljunk el a gyökérből, és lépkedjünk lefelé. Mindig azt az irányt válasszuk, ahol Δmin(x) = 0. Ha ez mindkét gyerekre igaz, akkor lépjünk jobbra (mert az utolsó jó csúcs kell nekünk). Akkor állunk meg, ha a Δcost érték is 0 lesz, és nincs jobb gyerek, vagy annak Δmin értéke nem 0. path_addcost(p,x): Csak a gyökércsúcs Δmin értékéhez kell hozzáadni x-et. (O(1) idejű!) Ugyanis minden költséget ahhoz viszonyítva számolunk. Vagyis egy csúcs költségének lekérdezése O(1) helyett O(log n) idejű lett, cserébe viszont a fenti két művelet O(n) helyett O(log n). 18
Bináris fák kiegyensúlyozása Fontos a bináris fák kiegyensúlyozottsága, amely a dinamikus műveletek (path_concat és path_split) hatására elromolhat. Egy egyszerű forgatás O(1) időben megvalósítható (a Δcost és Δmin értékeket is módosítani kell!). Forgatásokkal biztosíthatjuk a fa kiegyensúlyozottságát úgy, ahogy a keresőfáknál (AVL-fa, piros-fekete fa, önkiegyensúlyozó fa stb.). Így minden útműveletre garantálható az O(log n) korlát. Most térjünk vissza a dinamikus fákra adott struktúrához. m faművelethez O(m log n) útművelet kellett, ami ezek alapján O(m (log n) 2 ) idő. Ha az utak megvalósításához önkiegyensúlyozó fákat használunk, akkor ennél egy log n faktorral jobb lépésszámkorlát adható. Vagyis az O(m log n) path_concat és path_split művelet összesen csak O(m log n) lépés, amit korábban ígértünk. 19
Megjegyzések Mivel egy utat egyértelműen meghatároz az őt reprezentáló bináris fa gyökere, az útra vonatkozó successor mutatót tárolhatjuk ennél a csúcsnál. A bemutatott dinamikus fa adatszerkezetnek több más változata is ismert: Az adatszerkezet kiegészíthető további műveletekkel. Az utakra bontást bonyolultabban végezve elérhető, hogy minden műveletre külön teljesüljön az O(log n) korlát. Van olyan változat, amelyben az éleknek van költsége, nem a csúcsoknak. Ilyenkor bonyolultabb bináris keresőfákat kell alkalmazni az utak reprezentálására. 20
Felhasznált irodalom Robert E. Tarjan: Data Structures and Network Algorithms. Society for Industrial and Applied Mathematics, Daniel D. Sleator – Robert E. Tarjan: A Data Structure for Dynamic Trees. Journal of Computer and System Siences, 26(3): , Ravindra K. Ahuja – Thomas L. Magnanti – James B. Orlin: Network Flows: Theory, Algorithms, and Applications. Prentice-Hall, Inc.,