Stáhnout prezentaci
Prezentace se nahrává, počkejte prosím
1
Výška stromu - algoritmus
function BTHeight (uzel:tBTNodePtr):integer; var VyskaL,VyskaR:integer; PomUzel:tBTNode; begin if uzel<>Nil then with uzel^ do VyskaR:=BTHeight(RPtr); {zjisti výšku pravého a } VyskaL:=BTHeight(LPtr); {levého podstromu} if VyskaL>VyskaR then BTHeight:=VyskaL+1 {celk. výška je ta větší} else BTHeight:=VyskaR+1; {z nich + výška otcovského uzlu} end {která je zřejmě =1} else BTHeight:=0; {výška prázdného stromu je 0} end;
2
Nerekurzivní operace nad stromem
Hodí se, pokud potřebujete implementovat strom na platformě nebo OS, kde je omezená velikost HW zásobníku, popř. v jazycích, kde není možná rekurze Místo rekurzivního volání se strom prochází ve smyčce K postupnému zanořování a vynořování se používá ADT Zásobník, u nějž můžete dynamicky kontrolovat velikost alokované paměti (u HW zásobníku způsobí přetečení okamžité ukončení vašeho programu, pričemž nemáte možnost na toto reagovat, ani zjistit, kolik ještě paměti na HW zásobníku zbývá )
3
Nerekurzivní operace nad stromem
Operace Insert a Search nepotřebují zásobník Search: procházej strom od kořene podle pravidel binárního stromu (menší prvky jsou vlevo, větší vpravo) pokud najdeš prvek se stejnou hodnotou, skonči Insert: Pokud chceš přejít z akutálního uzlu na další uzel, ale daný ukazatel (ukLevy nebo ukPravy je nil), pak vytvoř nový prvek, vlož do něj požadovaná data, a napoj jej na tento ukazatel. Operace Preorder, Inorder a Postorder již vyžadují použití zásobníku:
4
Nerekurzivní operace nad stromem
Preorder: Je potřebná podpůrná procedura LevaDiagonala, která od kořene postupně projde, vypíše a uloží na zásobník ukazatele na všechny levé uzly, dokud nedojde k poslednímu, nejlevějšímu listu Na začátku volej ProjdiLeve(..) Dokud není zásobník prázdný, vyzvedávej z něj ukazatele na jednotlivé uzly, a volej LevaDiagonála na pravé syny těchto uzlů procedure Preorder(RootPtr:tUkUzel); procedure ProjdiLeve(RootPtr:tUkUzel); begin while RootPtr<>nil do begin Push(Stack,RootPtr); WriteUzel(RootPtr); RootPtr:=RootPtr^.ukLevy; end; Init(Stack); ProjdiLeve(RootPtr); while not Empty(Stack) do ProjdiLeve(Pop(PStack)^.ukPravy);
5
Nerekurzivní operace nad stromem
Inorder je v podstatě shodný s pre-orderem, jediný rozdíl je v umístění akce WriteUzel, která sem v Inorderu volá po každé, když vyzvedneme ze zásobníku ukazatel na uzel z levé diagonály procedure Preorder(RootPtr:tUkUzel); procedure ProjdiLeve(RootPtr:tUkUzel); begin while RootPtr<>nil do begin Push(Stack,RootPtr); RootPtr:=RootPtr^.ukLevy; end; Init(Stack); ProjdiLeve(RootPtr); while not Empty(Stack) do begin RootPtr:=Pop(Stack); WriteUzel(RootPtr); ProjdiLeve(RootPtr^.ukPravy); end;
6
Nerekurzivní operace nad stromem
Postorder je nejsložitější, protože musí nejdřív projít levý a pravý podstrom a teprve potom zpracovat obsah uzlu. Proto kromě zásobníku s ukazateli na jednotlivé uzly potřebujeme ještě zásobník příznaků, zda už byl daný uzel projden zleva i zprava. Popřípadě stačí jeden zásobník, jehož položky ale budou obsahovat ukazatel+příznak. Příklad se 2ma zásobníky: procedure Preorder(RootPtr:tUkUzel); procedure ProjdiLeve(RootPtr:tUkUzel); begin while RootPtr<>nil do begin PushUk(StackUk,RootPtr); PushPrizn(StackPrizn,true); {priznak: pruchod pouze zprava} RootPtr:=RootPtr^.ukLevy; end; InitUk(StackUk); InitPrizn(StackPrizn); ProjdiLeve(RootPtr); while not EmptyUk(StackUk) do begin RootPtr:=PopUk(StackUk); if PopPrizn(StackPrizn) then {pokud tento uzel ještě nebyl projden zprava} PushPrizn(StackPrizn,false); {označ jej jako projdený} PushUk(StackUk,RootPtr); {a projdi levou diagonálu jeho pravého syna} ProjdiLeve(RootPtr^.ukPravy); end else ProjdiLeve(RootPtr^.ukPravy);{pokud se vracíš zprava, vytiskni obsah otce}
7
Vyvážené stromy rodič potomek Zavedení potřebných pojmů:
Potomek uzlu X je uzel, který je navázán na uzel X přes pravý nebo levý ukazatel. Rodič uzlu X je uzel, jehož je uzel X potomkem. rodič 15 8 potomek List je uzel stromu, který nemá potomky Kořen je uzel stromu, který nemá rodiče Výška stromu je délka nejdelší cesty od kořene k listu Výška ideálně vyváženého stromu <= log2(N)+1, kde N=počet prvků stromu Míra nevyvážení = výška stromu-výška ideálně vyváženého stromu
8
Automatické vyvažování stromů
Stromy s automatickým vyvažováním podstromů představují způsob, jak udržovat stromy vyvážené (tzn. zabránit degeneraci některého z podstromů na lineární seznam) Statické vyvažování stromů: pokud překročí míra nevyvážení určitou mez, uloží se uzly stromu do seřazeného pole (=průchodem inorder) a původní strom se smaže. Následně se z prvků pole vytvoří nový strom. Aby byl vyvážený, je nutné pole projít metodou „půlení intervalu“. Příklad: 15 8 4 10 13 14 11 1. 2. 3. 4. 5. 6. 7. Pořadí vkládání do nového stromu:
9
Stavba stromu průchodem seřazeným polem metodou půlení intervalu
procedure puleni(i1,i2:integer; var pole:tPole; var strom:tUkUzel); var pulka:integer; begin if i1<i2 then begin pulka:=(i2-i1) div 2; InsertTree (strom, pole[i1+pulka]); puleni(i1,i1+pulka); puleni(i1+pulka+1, i2); end; puleni(1, N+1, p, s);
10
Statické vyvažování stromů
Aby nebylo potřeba při každém vyvažování zjišťovat míru vyvážení celého stromu, je vhodné udržovat v každém uzlu informaci o míře vyvážení, která se musí aktualizovat při každém vkládání/rušení uzlu. Nevýhody metody: Uložení stromu do pole = N kroků + paměť N*(velikost položky) Konstrukce stromu z pole = N kroků Pokud strom obsahuje miliony položek, pak opakované statické vyvažování bude vyžadovat nezanedbatelný čas pro zpracování
11
AVL stromy Princip: k degeneraci stromu až na lineární seznam dochází vlivem operací Insert a Delete. Musí tedy existovat způsob, jak tyto procedury implementovat tak, aby k degeneraci nedocházelo. Pánové Adel'son, Velskii & Landis zjistili, že udržet strom ideálně vyvážený by vyžadovalo nepřijatelně velké režie u operací Insert a Delete - složitost těchto operací by se v nejhorším případě blížila N – u ideálně vyváženého stromu mají tyto operace složitost log2(N) Navrhli proto volnější podmínku pro vyváženost stromů - pro všechny uzly stromu musí platit:výška pravého podstromu=výška levého podstromu±1 Operace search v AVL (Adel'son-Velskii & Landis) stromech má složitost nejvýše 2.log2(N), operace Insert a Delete mají složitost 2.log2(N)+K, kde K je režie rotací Modifikace algoritmu AVL „Red Black“ ještě dále tuto složitost vylepšuje
12
AVL stromy Princip úprav v metodách Insert a Delete
V každém uzlu si budeme udržovat další datovou položku „balanc“ o vyvážení tohoto uzlu. Pokud balanc=0, je uzel vyvážen (pravý i levý podstrom mají stejnou výšku). Pokud balanc=-1, je levý podstrom uzlu vyšší o 1. Pokud uzel=1, je pravý podstrom uzlu vyšší o 1. Po každém vložení nového uzlu upravíme hodnotu „balanc“ všech nadřazených uzlů. Pokud dojde u některého z uzlů k porušení podmínky AVL (balanc=±2), musíme provést některou z rotací, které upraví rovnováhu takovéhoto stromu. Rotace provádíme na uzlu s porušenou rovnováhou, který je nejdál od kořene = kritický uzel. Kritický uzel
13
AVL stromy LL a RR rotace:
Pokud měly podstromy T1, T2 a T3 na před operací stejnou výšku, a po vložení nového prvku se výška T1 zvýšila o 1, můžeme tuto situaci vyřešit rotací LL dle následujícího obrázku (stranově symetrická situace se řeší stranově symetrickou rotací RR):
14
AVL stromy LR a RL rotace:
Pokud měly podstromy T1, T2 a T3 na před operací stejnou výšku, a po vložení nového prvku se výška T2 zvýšila o 1, můžeme tuto situaci vyřešit rotací LR dle následujícího obrázku (stranově symetrická situace se řeší stranově symetrickou rotací RL):
Podobné prezentace
© 2024 SlidePlayer.cz Inc.
All rights reserved.