Reprezentace aritmetického výrazu - binární strom reprezentující aritmetický výraz (2 + 5) * (13-4) * + - 2 5 13 4 - listy stromu obsahují operandy (čísla) - vnitřní uzly obsahují operátory (znaménka) - závorky ve stromě nejsou, pořadí vyhodnocení je určeno strukturou stromu Pavel Töpfer, 2019 Programování 2-11 1
type Uk = ^Uzel; Uzel = record Znam: char; {znaménko + - * /} Hod: real; {hodnota uložená v listu} L, R: Uk {levý a pravý syn} end; - neukládají se závorky (uzávorkování je kódováno strukturou stromu) - je třeba odlišit listy (např. položka Znam = @, nebo stačí L = nil) (2 + 5) * (13-4) * + - 2 5 13 4 Pavel Töpfer, 2019 Programování 2-11 2
Vyhodnocení aritmetického výrazu reprezentovaného binárním stromem rekurzívně (metoda Rozděl a panuj): function Vyhodnot(K: uzel): real; {K je ukazatel na kořen stromu} begin if K je list then Vyhodnot := hodnota listu K else begin L := Vyhodnot(levý podstrom uzlu K); R := Vyhodnot(pravý podstrom uzlu K); Vyhodnot := s hodnotami podstromů proveď operaci určenou znaménkem v uzlu K; end end; Pavel Töpfer, 2019 Programování 2-11 3
function Vyhodnot(K: Uk): real; {K je ukazatel na kořen stromu} begin with K^ do case Znam of '@': Vyhodnot := Hod; '+': Vyhodnot := Vyhodnot(L) + Vyhodnot(R); '-': Vyhodnot := Vyhodnot(L) - Vyhodnot(R); '*': Vyhodnot := Vyhodnot(L) * Vyhodnot(R); '/': Vyhodnot := Vyhodnot(L) / Vyhodnot(R) end end; Pavel Töpfer, 2019 Programování 2-11 4
Notace aritmetického výrazu - průchod binárním stromem reprezentujícím aritmetický výraz - v navštívených uzlech vypisujeme uloženou hodnotu (2 + 5) * (13-4) * + - 2 5 13 4 průchod preorder PREFIX * + 2 5-13 4 průchod inorder INFIX (bez závorek!) 2 + 5 * 13-4 průchod postorder POSTFIX 2 5 + 13 4 - * Pavel Töpfer, 2019 Programování 2-11 5
průchod preorder PREFIX * + 2 5-13 4 průchod inorder INFIX (bez závorek!) 2 + 5 * 13-4 průchod postorder POSTFIX 2 5 + 13 4 - * - vždy stejné pořadí operandů listy stromu procházíme ve všech případech zleva doprava - v prefixovém zápisu operátor bezprostředně předchází své dva operandy, v postfixovém je následuje - v prefixovém a postfixovém zápisu výrazu nejsou závorky, pořadí vyhodnocování je plně určenou strukturou výrazu - inorder průchod stromem vytvořil chybný infixový zápis bez závorek, z něhož není zřejmé pořadí vyhodnocování výrazu Terminologická poznámka: prefix = polská notace (Polish notation) Łukasiewicz postfix = reverzní polská notace (reverse Polish notataion, RPN) Pavel Töpfer, 2019 Programování 2-11 6
Získání správného infixového zápisu výrazu: procedure Infix(K: uzel); begin if K je list then write(hodnota uzlu K - číslo) else begin write('('); Infix(levý syn uzlu K); write(hodnota uzlu K - znaménko); Infix(pravý syn uzlu K); write(')') end end; Pavel Töpfer, 2019 Programování 2-11 7
Vyhodnocení výrazu v postfixové notaci - snadné, využití např. u kalkulaček, v překladačích - jeden průchod zápisem výrazu zleva doprava - zásobník na ukládání číselných hodnot Postup zpracování postfixového zápisu: číslo vložit do zásobníku znaménko vyzvednout ze zásobníku horní dvě čísla provést s nimi operaci určenou znaménkem výsledek operace vložit do zásobníku konec na zásobníku je jediné číslo = hodnota výrazu - pozor na pořadí operandů u nekomutativních operátorů (na vrcholu zásobníku je pravý operand, pod ním levý) - časová složitost O(N), kde N je délka výrazu Pavel Töpfer, 2019 Programování 2-11 8
Vyhodnocení výrazu v prefixové notaci 1. možnost: - průchod výrazem odzadu, postup jako u postfixu - pouze se změní pořadí operandů při vyzvednutí ze zásobníku: na vrcholu zásobníku je levý operand, pod ním pravý - časová složitost O(N), kde N je délka výrazu Pavel Töpfer, 2019 Programování 2-11 9
2. možnost: - jeden průchod zápisem výrazu zleva doprava - zásobník na ukládání znamének a číselných hodnot Postup zpracování prefixového zápisu odpředu: - znaménko nebo číslo vložit do zásobníku - když se na vrcholu zásobníku sejdou dvě čísla vyzvednout je ze zásobníku, dále vyzvednout znaménko uložené pod nimi, provést s čísly operaci určenou znaménkem a výsledek operace vložit do zásobníku (což může opětovně vyvolat tentýž proces vyhodnocení) - konec na zásobníku je jediné číslo = hodnota výrazu - pozor na pořadí operandů u nekomutativních operátorů (na vrcholu zásobníku je pravý operand, pod ním levý) - časová složitost O(N), kde N je délka výrazu Pavel Töpfer, 2019 Programování 2-11 10
3. možnost: rekurze - rekurzivní funkce na vyčíslení prefixového zápisu od zadaného indexu - když je prvním znakem výrazu číslice, výrazem je jen jedno číslo funkce vrátí jeho hodnotu (a posune index za něj) - když je prvním znakem znaménko, funkce provede dvě rekurzivní volání a s výsledky vykoná operaci naznačenou tímto znaménkem - celkem se provede jeden průchod zápisem výrazu zleva doprava - časová složitost O(N), kde N je délka výrazu Pavel Töpfer, 2019 Programování 2-11 11
Převod infix postfix - jeden průchod zápisem výrazu zleva doprava, časová složitost O(N) - zásobník na ukládání znamének - v postfixovém zápisu jsou čísla ve stejném pořadí jako v infixovém, znaménka je třeba pozdržet na zásobníku Postup zpracování infixového zápisu: číslo zapsat přímo na výstup levá ( do zásobníku pravá ) ze zásobníku postupně přenést na výstup všechna znaménka až k nejbližší uložené levé závorce, pak levou ( ze zásobníku i pravou ) ze vstupu zrušit znaménko do zásobníku předtím ale ze zásobníku postupně přenést na výstup všechna znaménka vyšší nebo stejné priority, nejvýše ale k první uložené levé ( konec ze zásobníku přenést na výstup všechna znaménka Pavel Töpfer, 2019 Programování 2-11 12
Vyhodnocení výrazu v infixové notaci - spojení dvou předchozích algoritmů: * převod výrazu z infixu do postfixu v čase O(N) * vyhodnocení postfixové notace v čase O(N) celková časová i paměťová složitost O(N) - obě fáze výpočtu se mohu provádět * buď postupně (s uložením vytvořené postfixové notace výrazu) * nebo souběžně (vznikající postfixová notace se neukládá, ale rovnou průběžně vyhodnocuje) algoritmus používá dva zásobníky jeden na znaménka a druhý na čísla Pavel Töpfer, 2019 Programování 2-11 13
Postavení binárního stromu ze zápisu výrazu postfixová nebo prefixová notace algoritmus podobný jako při vyhodnocování výrazu, do zásobníku se vždy ukládá odkaz (adresu) na nově vytvořený uzel a místo provádění operací se uzly s operandy zapojují pod uzel s operátorem jako jeho synové infixová notace nejprve ji převedeme na postfixovou notaci Pavel Töpfer, 2019 Programování 2-11 14
Binární vyhledávací strom BVS (BST - binary search tree) - datová struktura pro ukládání a vyhledávání dat podle klíče - pro každý uzel platí: všechny záznamy uložené v levém podstromu mají menší klíč všechny záznamy uložené v pravém podstromu mají větší klíč (platí pro všechny záznamy v podstromu, nestačí jen pro syny!) při vyhledávání není třeba procházet celý strom, stačí projít jednu cestu od kořene k listu časová složitost vyhledávání je v nejhorším případě určena výškou stromu Pavel Töpfer, 2019 Programování 2-11 15
Výška H binárního stromu o N uzlech = délka nejdelší cesty z kořenu do listu minimum vyvážený strom N = 2 0 + 2 1 + 2 H = 2 H+1 1 maximum degenerovaný strom H N H log 2 N v průměrném případě výška O(log N) Pavel Töpfer, 2019 Programování 2-11 16
Hledání hodnoty v BVS function Hledej(P: Uk; H: integer): Uk; {v BVS s kořenem P hledá hodnotu H, vrací odkaz na nalezený uzel, resp. nil} var Sestup: boolean; begin Sestup := P <> nil; while Sestup do begin if H = P^.Hodnota then Sestup := false else if H < P^.Hodnota then P := P^.L else {H > P^.Hodnota} P := P^.R; if P = nil then Sestup := false end; Hledej := P; end; Pavel Töpfer, 2019 Programování 2-11 17
Rekurzivní řešení téže funkce: function Hledej(P: Uk; H: integer): Uk; {v BVS s kořenem P hledá hodnotu H, vrací odkaz na nalezený uzel, resp. nil} begin if P = nil then Hledej := nil else if H = P^.Hodnota then Hledej := P else if H < P^.Hodnota then Hledej := Hledej(P^.L, H) else {H > P^.Hodnota} Hledej := Hledej(P^.R, H) end; Pavel Töpfer, 2019 Programování 2-11 18
Přidání hodnoty do BVS - novou hodnotu musíme přidat tam, kde ji budeme hledat - přidává se vždy do nového listu - postupuje se stejně jako při hledání hodnoty v BVS, dokud se nenarazí na nil-ový ukazatel a do toho místa se přidá nový uzel - technická realizace: při průchodu stromem od kořene k listu udržovat pomocný ukazatel o jeden krok pozadu, pomocí něj pak připojit do stromu nový uzel - časová složitost je opět dána výškou stromu, v průměrném případě O(log N) Pavel Töpfer, 2019 Programování 2-11 19
Vypuštění hodnoty z BVS - nejprve průchodem od kořene směrem k listu najít uzel s vypouštěnou hodnotou (a jeho předchůdce ve stromě) - pokud je to list, zruší se (v předchůdci nastavit nil) - pokud má jen jednoho následníka, uzel se zruší a jeho následník se přepojí místo něj (předchůdce tedy bude místo na rušený uzel ukazovat na jeho jediného následníka) - pokud má dva následníky, uzel se nemůže fyzicky zrušit; smaže se jen jeho dosavadní hodnota a nahradí se jinou vhodnou hodnotou ze stromu. Tou je buď nejmenší hodnota z pravého podstromu nebo naopak největší hodnota z levého podstromu rušeného uzlu. Tato náhradní hodnota leží jistě v listu nebo v uzlu s jediným následníkem, její původní uzel tedy umíme snadno zrušit. Pavel Töpfer, 2019 Programování 2-11 20
- časová složitost vypuštění uzlu z BVS je opět dána výškou stromu, v průměrném případě O(log N) celkově se prošlo stromem pouze jednou od kořene k listu Pavel Töpfer, 2019 Programování 2-11 21
Hledání hodnoty v BVS s použitím zarážky (alternativa k předchozímu řešení) - jeden speciální uzel navíc slouží jako zarážka při hledání (vkládá se do něj hledaná hodnota) - všechny ukazatele L, R v uzlech stromu s hodnotou nil nahradíme ukazateli směřujícími na tento uzel function Hledej(P, Zarazka: Uk; H: integer): Uk; begin Zarazka^.Hodnota := H; while P^.Hodnota <> H do if H < P^.Hodnota then P := P^.L else {H > P^.Hodnota} P := P^.R; if P = Zarazka then Hledej := nil else Hledej := P; end; Pavel Töpfer, 2019 Programování 2-11 22
Vyvážené stromy cíl: zajistit výšku stromu O(log N) v případě BVS časová složitost všech operací O(log N) Dokonale vyvážený binární strom pro každý uzel platí: počet uzlů v jeho levém a pravém podstromu se liší nejvýše o 1 - nejlepší možné vyvážení, výška stromu s N uzly je log N - lze snadno postavit z předem známé množiny hodnot - je obtížné udržovat strom dokonale vyvážený při přidávání a odebírání hodnot proto se v praxi používají jiné (slabší) definice vyváženosti, strom nebude tak dokonale vyvážený, ale půjde snadněji udržovat Pavel Töpfer, 2019 Programování 2-11 23
AVL strom (G. M. Adeľson-Velskij, E. M. Landis, 1962) pro každý uzel platí: výška jeho levého a pravého podstromu se liší nejvýše o 1 - slabší požadavek, ale stačí: AVL-strom je maximálně o 45% vyšší než dokonale vyvážený strom se stejným počtem uzlů - každý dokonale vyvážený strom je AVL-stromem - AVL-strom nemusí být dokonale vyvážený Příklad: Pavel Töpfer, 2019 Programování 2-11 24
Postavení dokonale vyváženého binárního stromu s N uzly function DVBS(N: integer): Uk; var S: Uk; begin if N = 0 then DVBS:=nil else begin new(s); S^.L:=DVBS((N-1) div 2); S^.R:=DVBS(N-1 (N-1) div 2); DVBS:=S end end; Funkce vrací ukazatel na kořen sestrojeného stromu. Info-hodnoty uzlů ve stromu zatím nejsou definovány. Pavel Töpfer, 2019 Programování 2-11 25
Postavení dokonale vyváženého binárního vyhledávacího stromu s danými N hodnotami v uzlech 1. varianta řešení: - nejprve postavit dokonale vyvážený binární strom s N uzly pomocí předchozí funkce DVBS (hodnoty uzlů zatím nejsou definovány) - hodnoty setřídit - projít sestrojený strom metodou inorder a přitom do uzlů stromu postupně zapisovat hodnoty v pořadí od nejmenší po největší 2. varianta řešení: - hodnoty nejprve setřídit v poli A[1..N] - při konstrukci stromu rovnou vkládat do Info-položek uzlů hodnoty - parametry funkce Strom určují rozsah indexů v poli A (které hodnoty z pole A patří do příslušného podstromu) - funkce bude volána Strom(1,N), vrací ukazatel na kořen sestrojeného stromu Pavel Töpfer, 2019 Programování 2-11 26
function Strom(X, Y: integer): Uk; var S: Uk; begin if X > Y then Strom:=nil else begin new(s); S^.Info:=A[(X+Y) div 2]; S^.L:=Strom(X, (X+Y) div 2 1); S^.R:=Strom((X+Y) div 2 +1, Y); Strom:=S end end; Pavel Töpfer, 2019 Programování 2-11 27