Algoritmy a datové struktury Stromy 1 / 32
Obsah přednášky Pole a seznamy Stromy Procházení stromů Binární stromy Procházení BS Binární vyhledávací stromy 2 / 32
Pole Hledání v poli metodou půlení intervalu najít 18 1 3 2 5 8 10 12 15 18 20 22 24 Vkládání do pole vložit 19 19 5 8 10 12 15 18 20 22 24 3 / 32
Seznamy Vyhledávání v seznamu lineárním vyhledáváním najít 18 1 2 3 4 5 6 5 8 10 12 15 18 20 22 24 Vkládání do seznamu vložit 19 19 5 8 10 12 15 18 20 22 24 x 4 / 32
Porovnání Získat obě výhody rychlé vyhledávání snadné vkládání S polem moc nadějí nemáme Vylepšit seznam upravit seznam tak, aby místo na sousedy ukazovaly prvky na poloviny intervalů 5 8 10 12 15 18 20 22 24 5 / 32
Finální úprava 15 8 20 5 10 18 22 12 24 6 / 32
Co je strom? Strom je množina uzlů Existuje jeden startovací uzel kořen stromu Každý uzel kromě kořenu má rodiče Uzel může mít libovolné množství dětských uzlů pokud nemá děti, je to list 7 / 32
Co je strom? Přirozená rekurzivní datová struktura Zachycuje vztahy mezi prvky stromu Velké množství aplikací souborový systém dědičnost v OOP hokejová extraliga rodokmen... 8 / 32
Cesta k uzlu Posloupnost uzlů: kořen, u1, u2,... kde u1 je potomkem kořene, u2 potomkem u1,... Ke každému uzlu existuje právě jedna cesta kdyby ne, nebyl by to strom Délka cesty od kořene k uzlu určuje jeho hloubku 9 / 32
Implementace Pomocí referencí - podobně jako seznamy u obecných stromů mohou být i odkazy na děti spojový seznam Hodnota Hodnota Hodnota Hodnota Hodnota Hodnota Hodnota 10 / 32
Procházení stromů Pre-order Vypíše kořen stromu, rekurzivně pokračuje pro všechny podstromy Preorder ( uzel v) vypis (v); foreach ( detskyuzel u in v) Preorder (u); Aplikace: strukturované dokumenty (XML) Kniha Kapitola 1 Kapitola 2 Kapitola 3 Podkapitola 1.1 Podkapitola 1.2 Podkapitola 2.1 11 / 32
Procházení stromů Post-order Vypíše rekurzivně všechny podstromy, nakonec vypíše kořen Postorder ( uzel v) foreach ( detskyuzel u in v) Postorder (u); vypis (v); Aplikace: zjištění velikosti podadresářů na disku c:\ Temp Windows Program data.txt win.com win.bmp Kresleni Psani 12 / 32
Procházení stromů Level-order Vypíše všechny uzly se stejnou hloubkou Aplikace: hledání nejkratší cesty A C B D B 13 / 32
Prohledávání stromů do hloubky Depth-First Search (DFS) Nejprve se navštíví všechny děti, potom rodič Příklad 3 10 8 12 7 4 3 6 5 5 8 12 4 6 3 10 7 3 14 / 32
Prohledávání stromů do šířky Ekvivalent level-order Breadth-First Search (BFS) Navštíví všechny uzly se stejnou hloubkou Příklad 5 8 12 4 6 3 10 7 3 5 8 12 4 6 3 10 7 3 15 / 32
Definice Rekurzivní definice binární strom je prázdný strom, nebo uzel obsahující klíč, data, a levý a pravý binární podstrom Rekurzivní přístup s výhodou využijeme i při práci se stromem 16 / 32
Příklad class BinarniUzel public Klic klic; public BinarniUzel levy,pravy; public BinarniUzel(Klic klic) this.klic = klic; levy = null; pravy = null; class BinarniStrom public BinarniUzel koren; public BinarniStrom(Klic klic) koren = new BinarniUzel(klic); 17 / 32
Procházení stromů In-order Vypíše rekurzivně všechny podstromy, nakonec vypíše kořen Inorder ( uzel v) Inorder (v. levy ); vypis (v); Inorder (v. pravy ); Aplikace: výpis aritmetických operací a (b + c). a + b c 18 / 32
Binární vyhledávací stromy Binary Search Tree (BST) Binární strom s vlastnostmi uzly obsahují klíče, u kterých lze určit relace <,>,= klíče uzlů U ležících nalevo od kořene K jsou menší než klíč kořene K klíče uzlů U ležících napravo od kořene K jsou větší než klíč kořene K 15 8 20 5 10 18 22 12 24 19 / 32
Vyhledávání v BST Začátek v kořenu stromu Porovnáme hodnotu klíče pokud se rovná hledané hodnotě, nalezli jsme pokud je menší, pokračujeme v levém podstromu pokud je větší, pokračujeme v pravém podstromu Dojdeme-li do listu, který je jiný než hledaná hodnota, strom hledaný prvek neobsahuje Příklad najít 18 1 15 2 8 20 3 5 10 18 22 12 24 20 / 32
Vyhledávání v BST public BinarniUzel Najdi ( Klic klic ) return Najdi ( koren, klic ); public BinarniUzel Najdi ( BinarniUzel uzel, Klic klic ) if( uzel == null ) return null ; if(klic < uzel. klic ) return Najdi ( uzel.levy, klic ); else if(klic > uzel. klic ) return Najdi ( uzel. pravy, klic ); else return uzel ; Složitost vyhledávání? Jak se změní když zvýšíme počet dětí? Jak se změní když snížíme počet dětí? 21 / 32
Nalezení nejmenšího/největšího prvku Rekurzivně dojít k nejlevějšímu/nejpravějšímu uzlu stromu public BinarniUzel Min () BinarniUzel min = null ; for ( BinarniUzel u= koren ; u!= null ; u=u. levy ) min =u; return min ; 22 / 32
Vkládání prvku do BST Nalezení místa vhodného pro prvek Pokud už prvek existuje pak nedělat nic Pokud neexistuje, vložit prvek na nalezené místo Příklad vložit 9 15 8 20 5 10 18 22 9 12 24 23 / 32
Vkládání prvku do BST public void Pridej ( Klic klic ) BinarniUzel aktualni, rodic ; aktualni = koren ; rodic = null ; while ( aktualni!= null ) rodic = aktualni ; if(klic < rodic. klic ) aktualni = aktualni. levy ; if(klic > rodic. klic ) aktualni = aktualni. pravy ; if( klic == rodic. klic ) break ; if( rodic == null ) koren = new BinarniUzel ( klic ); if(klic < rodic. klic ) rodic. levy = new BinarniUzel ( klic ); if(klic > rodic. klic ) rodic. pravy = new BinarniUzel ( klic ); 24 / 32
Mazání prvku Při mazání je nutné postarat se o děti Musí být zachovány vlastnosti BST Jaké situace mohou nastat? mazaný uzel nemá žádné potomky mazaný uzel má pouze jednoho potomka mazaný uzel má oba potomky Nebo mazaný uzel nemá levého potomka mazaný uzel nemá pravého potomka mazaný uzel má oba potomky 25 / 32
Nalezení mazaného prvku BinarniUzel aktualni, rodic ; aktualni = koren ; rodic = null ; while (( aktualni!= null )&&( aktualni. klic!= klic )) rodic = aktualni ; if(klic < rodic. klic ) aktualni = aktualni. levy ; if(klic > rodic. klic ) aktualni = aktualni. pravy ; if( klic == rodic. klic ) break ; if( aktualni == null ) prvek_ nenalezen 26 / 32
Uzel nemá levého potomka Rodičovskému uzlu přiřadíme místo mazaného prvku jeho pravého potomka if( aktualni. levy == null ) if(klic < rodic. klic ) rodic. levy = aktualni. pravy ; else rodic. pravy = aktualni. pravy ; 5 x 8 15 27 / 32
Uzel nemá pravého potomka Rodičovskému uzlu přiřadíme místo mazaného prvku jeho levého potomka else if( aktualni. pravy == null ) if(klic < rodic. klic ) rodic. levy = aktualni. levy ; else rodic. pravy = aktualni. levy ; 28 / 32
Uzel má oba potomky Rodičovskému uzlu přiřadíme nejlevější prvek z pravého potomka else BinarniUzel nejlevejsi = aktualni. pravy. levy, nejlevejsirodic = aktualni. pravy ; while ( nejlevejsi. levy!= null ) nejlevejsirodic = nejlevejsi ; nejlevejsi = nejlevejsi. levy ; nejlevejsirodic. levy = nejlevejsi. pravy ; nejlevejsi. levy = aktualni. levy ; nejlevejsi. pravy = aktualni. pravy ; if(klic < rodic. klic ) rodic. levy = nejlevejsi ; else rodic. pravy = nejlevejsi ; 8 x 9 15 29 / 32
Líné mazání prvku Pro velké stromy může být mazání poměrně náročné Místo fyzického smazání označit uzel jako neplatný, ale ponechat ho ve stromu Problémy? zvyšuje se hloubka stromu (nebo spíš nesnižuje se při mazání) pro velké stromy u kterých je počet smazaných prvků srovnatelný s počtem stávajících prvků to není kritické 30 / 32
Všechno funguje pěkně ale... Co se stane když budou vrcholy vkládány v nešikovném pořadí Příklad: 5 7 10 11 13 jaká je složitost hledání? co se s tím dá vymyslet? 5 7 10 11 13 31 / 32
Konec 32 / 32