Programování 3. hodina RNDr. Jan Lánský, Ph.D. Katedra informatiky a matematiky Fakulta ekonomických studií Vysoká škola finanční a správní 2015
Umíme z minulé hodiny Implementace zásobníku a fronty pomocí jednosměrných spojových seznamů Obousměrné lineární spojové seznamy Create, ConvertArray, Print InsertToBegin, InsertToEnd InsertBefore, InsertAfter, Find, Delete Cyklické spojové seznamy Jan Lánský Programování 3. hodina 2
Cíle hodiny Stromy Reprezentace Binární vyhledávací stromy Create Find, Insert, Delete Vypis (inorder, preorder, postorder) Vyvážené stromy (stručně) Dokonale vyvážený binární vyhledávací strom AVL strom Jan Lánský Programování 3. hodina 3
Formálně: Zakořeněný strom je souvislý acyklický orientovaný graf Strom Vrchol (matematický pojem) se v programování někdy označuje jako uzel. Struktura skládající se z uzlů a hran. Hrana vede z jednoho uzlu do jiného uzlu. Uzel, ze kterého vede hrana, je otcem vrcholu, do kterého hrana vede. Rekurzivní definice: Strom má právě jeden uzel zvaný kořen. Synové kořene jsou kořeny vlastních stromů. Z kořene do libovolného uzlu vede vždy právě jedna cesta (posloupnost hran, které se neopakují) Uzel, který nemá žádné syny se nazývá list. Jan Lánský Programování 3. hodina 4
Používá se terminologie: otec, bratr, dědeček, vnuk, bratranec, strýc, Strom Kořen A má syny B, C a D Uzel B má syna E A Uzel D má syny F a G B C D Uzly C, E, F a G nemají žádné syny, jsou to listy Hrana mezi D a F E F G Větev: cesta od kořene k listu, např. A, D, F Jan Lánský Programování 3. hodina 5
Speciální druh stromu: Každý vrchol má nejvýše dva syny Binární strom Kořen A má syny 2 syny A Uzel D má 2 syny List C má 0 synů C D List F má 0 synů F G List G má 0 synů Jan Lánský Programování 3. hodina 6
Jednosměrný spojový seznam je strom, obousměrný strom není, obsahuje cyklus Stromy v běžném životě Říční síť (obecný strom) Pokud se řeka vlévá do jiné řeky je jejím synem. Rodokmeny Typ rozvod: (obecný strom) naše názvosloví Typ vývod z předků: (binární strom) Otec a matka jsou synové svého dítěte Struktura řízení ve firmě (obecný strom) Ředitel je kořen, řadoví zaměstnanci listy Play-off pavouk (binární strom) Vítěz je otcem (své kopie a poraženého soupeře) Jan Lánský Programování 3. hodina 7
Reprezentace: Binární strom V praxi hodně využívané pro snadnou implementaci Uzel při implementaci se používá tento název pro vrchol Data, která uzel obsahuje Odkaz na levého syna Odkaz na pravého syna Jan Lánský Programování 3. hodina 8
Binární strom Kořen 213 705 45 null null List List 2 105 null null null null List Jan Lánský Programování 3. hodina 9
Reprezentace: Obecný strom pole synů (*) Maximální počet synů je dán velikostí pole. Pokud chceme přidat více synů, musíme pole zvětšit. (*) Má-li uzel méně synů než je velikost pole (obvyklý stav) natavíme dalšího syna (prvek pole) na null. (*) Ideální pokud se od vytvoření uzlu synové již nemění. (*) Pokud syny jen přidáváme, je možné pole po naplnění zvětšit na dvojnásobek a syny překopírovat do nového pole (dynamické pole) Nevhodné, pokud převažují operace přidávání a ubírání synů Jan Lánský Programování 3. hodina 10
Reprezentace: Obecný strom seznam synů Spojový seznam synů Aktuální syn Následující syn (jeho bratr) Spojový seznam synů Reprezentace je vhodná při častých změnách struktury stromu Jan Lánský Programování 3. hodina 11
Reprezentace: Obecný strom převod na binární strom Obecný strom převedeme na binární strom První syn Následující bratr Chceme-li znát všechny syny uzlu, musíme z prvního syna uzlu projít všechny jeho bratry Jan Lánský Programování 3. hodina 12
K zamyšlení: Jak umožnit, aby více vrcholů mělo shodnou hodnotu AJ: binary search tree BST Binární vyhledávací strom Uzly stromu obsahují datovou položku, která vyjadřuje hodnotu vrcholu. Podle této hodnoty lze vrcholy porovnávat <, >, = Pro každý uzel "u" platí Všechny uzly nacházející se v podstromu levého syna mají menší hodnotu než je hodnota vrcholu "u" Všechny uzly nacházející se v podstromu pravého syna mají větší hodnotu než je hodnota vrcholu "u" Podmínky musí platit pro všechny vrcholu v podstromu (nikoliv jen pro syna) Jan Lánský Programování 3. hodina 13
Binární vyhledávací strom Všichni potomci < 40 40 Všichni potomci > 40 21 48 9 33 61 4 17 30 53 77 Zleva doprava: hodnoty uzlů tvoří vzestupně setříděnou posloupnost Reprezentace: použijeme stejnou třídu jako pro binární strom - StromB Jan Lánský Programování 3. hodina 14
Binární vyhledávací strom časová složitost operací V průměrném případě hloubka stromu O(Log N). Find, Insert, Delete O(log N) Oproti obousměrnému seznamu rychlejší hledání, za cenu zpomalení vkládání a mazání V nejhorším případě vznikne jednosměrný spojový seznam Při přidávání prvků v setříděném pořadí Find, Insert, Delete O(N) Jan Lánský Programování 3. hodina 15
Binární vyhledávací strom: Vytvoření Vytvoříme kořen reprezentující zadanou hodnotu. Kořen má syny null Binární vyhledávací strom je nutné vytvořit postupným vkládáním prvků. Pokud jsou prvky uloženy v poli, ušetříme si opakované psaní názvu funkce Insert při jejich vkládání. Insert bude o pár slajdů později Jan Lánský Programování 3. hodina 16
Binární vyhledávací strom: Find Praktická ukázka bude na tabuli Procházíme stromem od kořene směrem k listu, ve kterém by se hledaná hodnota měla nacházet. Pokud hodnota uzlu se rovná požadované hodnotě, vrátíme tento uzel. Pokud hodnota uzlu je větší než hledaná hodnota, pokračujeme v hledání v podstromu levého syna Pokud hodnota uzlu je menší než hledaná hodnota, pokračujeme v hledání v podstromu pravého syna Pokud je syn, jehož podstrom máme prohledat null, hledaný prvek se ve stromu nenachází Jan Lánský Programování 3. hodina 17
Binární vyhledávací strom: Find " akt" aktuální uzel stromu Podle hledané hodnoty se na konci iterace cyklu "akt" posune na levého nebo pravého syna Prvek se ve stromu nenachází Implementace pomocí rekurze. Rekurzivní volání funkce na levého nebo pravého syna Jan Lánský Programování 3. hodina 18
Binární vyhledávací strom: Insert Praktická ukázka bude na tabuli Podobně jako u funkce Find procházíme stromem od kořene směrem k listu, ve kterém by se hledaná hodnota měla nacházet. Hodnotu nalezeného listu, který je null, nahradíme novým uzlem s vkládanou hodnotou Při tomto průchodu nesmíme zapomenout otce nahrazovaného syna Při přechodu z otce na syna, pokud je tento syn null místo přechodu provedeme nahrazení. Jan Lánský Programování 3. hodina 19
Prázdný strom, vrátíme nový prvek, který se stane kořenem Průchod směrem od kořene k listům Prvek ve stromě už je Podle hodnoty uzlu nás zajímá levý syn Našli jsme místo pro vložení prvku. Levý syn je null, nahradíme ho nově vytvořeným prvkem a skončíme funkci Levý syn není ještě null, pokračujeme Pravý syn je symetrický levému Tento příkaz nikdy nenastane, ale kompilátor ho vyžaduje Jan Lánský Programování 3. hodina 20
Binární vyhledávací strom: Delete Praktická ukázka bude na tabuli. Obrázky v Prg Kucera.pdf (41-42) Nejprve najdeme uzel, který chceme smazat (pamatujeme si otce) Je-li mazaný uzel listem: nastavíme otci, že jeho příslušný syn je null Má-li mazaný uzel jednoho syna: Příslušného syna otce nastavíme na syna mazaného uzlu Otec mazaného uzlu bude mít za syna bývalého vnuka. Delete může smazat kořen, musí vracet hodnotu Jan Lánský Programování 3. hodina 21
Binární vyhledávací strom: Delete Praktická ukázka bude na tabuli. Obrázky v Prg Kucera.pdf (41-42) Má-li mazaný uzel dva syny Na pozici mazaného syna přesuneme (výměna odkazů, nikoliv hodnot) nejpravějšího potomka v levém podstromu mazaného uzlu Mazaný uzel na nové pozice ve stromu má maximálně jednoho syna, lze ho smazat již vysvětleným postupem. Analogicky lze nejlevějšího potomka v pravém podstromu Jan Lánský Programování 3. hodina 22
Výpis stromu v grafické podobě Výborné na ladění Počet hvězdiček reprezentuje počet předků uzlu Rekurzivně voláme na syny. Budou mít o hvězdičku více Kořen má hloubku 0 Jan Lánský Programování 3. hodina 23
Místo výpisu lze provést jinou akci, jde i pro nevyhledávací stromy Výpis setříděných hodnot Prvky binárního vyhledávacího stromu jsou směrem zleva doprava setříděné vzestupně. Výpis INORDER: Rekurzivně vypíšeme podstrom levého syna, poté uzel, poté podstrom pravého syna. výpis PREORDER: Nejprve vypíšeme uzel, poté rekurzivně podstrom levého syna, poté podstrom pravého syna, viz předchozí slajd (výpis v grafické podobě) Výpis POSTORDER: Rekurzivně vypíšeme podstrom levého syna, poté poté podstrom pravého syna, poté uzel. Jan Lánský Programování 3. hodina 24
Dokonale vyvážený binární vyhledávací strom jen stručně Snaha odstranit nejhorší případ, že z binárního vyhledávacího stromu vznikne spojový seznam Zaručena časová složitost Find O(log N) Při Insert a Delete nutno konstruovat strom znova - časová složitost O(N log (N)) Pro každý vrchol platí, že počet vrcholů v jeho pravém i levém podstromu se liší maximálně o jedna. Konstrukce: Zadána posloupnost prvků, setřídíme ji Zvolíme kořen jako prostřední prvek, posloupnost prvků se rozdělí na dvě části lišící se počtem prvků maximálně o 1 Rekurze pro levého (na prvky nalevo od kořene) a pravého syna (na prvky napravo od kořene) Jan Lánský Programování 3. hodina 25
Adeľson-Velskij, Landis, 1962 AVL strom jen stručně Snaha odstranit nejhorší případ, že vznikne spojový seznam Zaručena časová složitost Find, Insert i Delete O(log N) Pro každý uzel platí, že výška levého a pravého podstromu se liší maximálně o jedna Při vkládání a mazání prvků, pokud by došlo k porušení pravidla, se provádí jednoduchá (LL, RR) nebo dvojitá rotace (LR, RL) Pro zájemce podrobněji: http://www.cs.vsb.cz/kratky/courses/2004-05/udp/presentation/udp-8_6.pdf Jan Lánský Programování 3. hodina 26
Zpětná vazba Objevili jste ve slajdech chyby? Včetně pravopisných Nechápete nějaký slajd? Je příliš obtížný, nesrozumitelný? Máte nějaký nápad na vylepšení? Anonymní formulář Odeslání za pár vteřin http://goo.gl/forms/wjx99cbyy9 Jan Lánský Programování 3. hodina 27