Binární vyhledávací strom pomocí směrníků Miroslav Hostaša L06620 1. Vymezení pojmů Strom: Strom je takové uspořádání prvků - vrcholů, ve kterém lze rozeznat předchůdce - rodiče a následovníky - syny. Každý prvek - vrchol, může mít nejvýše jednoho předchůdce a několik následovníků. Kořen: Kořenem nazýváme takový prvek - vrchol, který nemá předchůdce. V každém stromu se nachází jen jeden kořen. Listy: Listy jsou takové prvky - vrcholy, které nemají žádného následovníka. Má-li strom jen jeden prvek - vrchol, je tento kořenem i listem zároveň. Binární strom: Každý prvek - vrchol stromu má nejvýše dva následovníky. Binární vyhledávací strom: Je binární strom, pro jehož každý uzel platí, že jeho levý podstrom je buď prázdný, nebo sestává z uzlů, hodnoty jejichž klíčů jsou menší než hodnota klíče daného uzlu. Podobně jeho pravý podstrom je buď prázdný, nebo sestává z uzlů, hodnoty jejichž klíčů jsou větší než hodnota klíče daného uzlu. Příklad binárního vyhledávacího stromu (obr.1) obr.1 Stručně: - každý prvek stromu = vrchol - vrchol '8' = kořen - vrcholy '1', '4', '7', '13' = listy - všechny vrcholy nalevo od kořene jsou menší než kořen - všechny vrcholy napravo od kořene jsou větší než kořen - stejný princip řazení platí u všech uzlů celého stromu 2. Realizace binárního vyhledávací stromu pomocí dynamických datových struktur
Velikost stromu nemusí být konečná, proto se pro realizaci velmi často využívá dynamických datových struktur. Dynamický datový typ definovaný pro tuto realizaci má dvě části, datovou a vazební - stejně jako například při realizaci seznamu dynamickou proměnnou. Rozdíl v proměnné, použité u binárního stromu, je pouze ten, že vazební část neobsahuje jeden ukazatel ale dva (každý uzel binárního stromu má právě dva syny). První ukazatel je vazbou na levého syna uzlu, druhý ukazatel je vazbou na pravého syna uzlu. V této nejjednodušší podobě pak lze takový dynamický datový prvek deklarovat například takto: Type TStrom=^vrchol; vrchol = record hodnota: Integer; levy, pravy: TStrom hodnota - data vrcholu levy, pravy - ukazatelé na syny Schématický popis realizace binárního vyhledávacího stromu z 'obr.1' pomocí dynamické datové struktury je na 'obr.2'. obr.2 Při vlastním použití takto deklarované dynamické proměnné je pak 'ukazatel vlevo' vazbou na syna s nižší hodnotu, 'ukazatel vpravo' pak vazbou na syna s vyšší hodnotou. Pokud uzel nemá syna pak nabývá ukazatel hodnoty NILL (stejně jako konec při použití dynamické proměnné v lineárním seznamu).
3. Vkládání prvku do binárního vyhledávacího stromu Z výše popsaných pravidel binárního stromu je zřejmý i způsob vkládání prvků do stromu. Popišme proto pouze stručně v bodech možné stavy a způsob vložení prvku. a) Strom je prázdný - první prvek se stává kořenem, synové jsou NIL b1) Strom má kořen - pokud je nový prvek menší než kořen a levý syn kořene je NIL pak se nový prvek stává levým synem (stává se listem), jeho oba dva synové jsou NIL. b2) Strom má kořen - pokud je nový prvek větší než kořen a pravý syn kořene je NIL pak se nový prvek stává pravým synem (stává se listem), jeho oba dva synové jsou NIL. c) Strom má kořen, má i své syny - postupuje se rekurzivně směrem dolu po větvích stromu podle bodu b1) a b2) Příklad procedury vkládání do které se vstupuje s hodnotou prvku: Procedure PridejVrchol (n:integer; var T:TStrom); if T=nil then new(t); T^.hodnota:=n; T^.levy:=nil; T^.pravy:=nil; if n < T^.hodnota then PridejVrchol(n, T^.levy); if n > T^.hodnota then PridejVrchol(n, T^.pravy); //pokud je strom prázdný nebo je odkaz na syna NILL pak // nový vrchol // hodnota novému vrcholu // pravý i levý syn se novému vrcholu nastaví na NILL //pokud je vkládaný prvek menší než vrchol pak //se rekurzivně voláme že chceme vložit prvek vlevo //pokud je vkládaný prvek větší než vrchol pak //se rekurzivně voláme že chceme vložit prvek vpravo
4. Rušení vrcholu v binárního vyhledávacím stromu Rušení prvku - vrcholu v binárním vyhledávacím stromu se řeší opět rekurzivně. Metoda rušení vrcholu má tyto pravidla. a) vrchol je listem - vrchol se zruší a odkaz na tento vrchol se nahradí NIL b) vrchol je uprostřed stromu a má jednoho syna - vrchol se zruší, na jeho místo se přesune jeho syn c) vrchol je uprostřed stromu a má oba syny - zde jsou přípustná dvě možná řešení 1) vrchol se zruší a na jeho místo se přesune nejlevější pravý list 2) vrchol se zruší a na jeho místo se přesune nejpravější levý list Schematický popis rušení vrcholu, který má dva syny podle varianty 1) na' obr.3'a, 'obr.3b'' obr.3a obr.3b Schematický popis rušení vrcholu, který má dva syny podle varianty 2) na 'obr.4a' a 'obr.4b' obr.4a obr.4b
Příklad procedury rušení prvku: Procedure OdeberVrchol (n:integer; var T:TStrom); Function NahradMaxPravymZleva(var T: TStrom): Integer; if T^.pravy=nil then result:=t^.hodnota; KeSmazani:=T; T:=T^.levy; dispose(kesmazani); result:=nahradmaxpravymzleva(t^.pravy); //procedura rekurzivně hleda v levé části //prvek který je nejvíce vpravo //pokud ho najde pak //si zapamatuje hodnotu prvku //prvek si připraví ke smazání //pokud měl prvek levého syna pak jde na // jeho místo //a prvek připravený ke smazání se smaže //pokud nejsme ještě na posledním prvku //pak rekurzivně ještě dál vpravo if T<>NIL then //hledej pokud není konec větve if n<t^.hodnota then //jestliže je hledana hodnota menší tak //rekurzivně dolů doleva OdeberVrchol(n,T^.levy); if n>t^.hodnota then //jestliže je hledana hodnota větší tak //rekurzivně dolů doprava OdeberVrchol(n,T^.pravy); if (T^.levy=NIL) and (T^.pravy=NIL) then //pokud jsme hodnotu našli a je to list //pak se může přímo zrušit dispose(t); T:=NIL; if (T^.levy=NIL) then //pokud nemá leveho syna pak prvek smazat //a na jeho místo jeho pravý syn KeSmazani:=T; T:=T^.pravy; dispose(kesmazani); if (T^.pravy=NIL) then //pokud nemá pravého syna pak prvek smazat //a na jeho místo jeho levý syn KeSmazani:=T; T:=T^.levy; dispose(kesmazani); //pokud má prvek oba syny pak volat funkci T.hodnota:=NahradMaxPravymZleva(T.levy); //která najde nejlevější pravou prvek, //ten zruší a vrátí jeho hodnotu, kterou se //nahradí hledaná hodnota
Popis řešení. Procedura 'OdeberVrchol' se volá s hledanou hodnotou vrcholu. Spustí se rekurzivní prohledání stromu na přítomnost této hodnoty. Postupuje se rekurzivně dolu od kořene, podle pravidel binárního vyhledávacího stromu. Pokud není prvek nalezen neprovede se nic. Pokud je prvek nalezen a nemá žádného potomka pak je odstraněn. Pokud je prvek nalezen a má pouze jednoho syna pak se prvek přesune do dočasné proměnné 'KeSmazani' a jeho místo nahradí jeho syn. Pak je prvek v proměnné 'KeSmazani' odstraněn. Pokud je prvek nalezen a má oba potomky, pak se volá funkce 'NahradMaxPravymZleva'. Tato funkce vyhledá nejlevější pravý prvek. Pokud je tento prvek listem, pak tento list zruší a jeho hodnotu předá zpět proceduře. Procedura touto hodnotu nahradí hodnotu v prvku ke zrušení (s ukazateli se v tomto případě nemanipuluje). Pokud má ale tento nejpravější prvek levého syna, pak se na místo rušeného prvku přesune tento syn.