Vyhledávání III. - Binární vyhledávací stromy
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

Vyhledávání III. - Binární vyhledávací stromyVyhledávání III. - Binární vyhledávací stromy

 

Vyhledávání III. - Binární vyhledávací stromy

Google       Google       20. 2. 2006       23 282×

Vyhledávání v BVS – operace SEARCH
Rekurzivní zápis vyhledávání
Nerekurzivní zápis vyhledávání
Vkládání prvku do BVS – operace INSERT
Rekurzivní zápis operace INSERT
Nerekurzivní zápis operace INSERT
Rušení prvku v BVS - operace DELETE
Rekurzivní zápis operace INSERT .

Binární vyhledávací strom (dále zkráceně BVS, anglicky – binary search tree) je takový binární uspořádaný 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 a 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.

V předcházející části kapitoly byla uvedena souvislost mezi nesekvenčním vyhledáváním v seřazeném poli a uspořádaným binárním stromem, která vyplývá ze stromové interpretace binárního a Fibonacciho vyhledávání. Vzájemnost tohoto vztahu doplňuje vlastnost průchodu typu INORDER binárním vyhledávacím stromem, kterým získáme seřazený lineární seznam.

Protože základní operací nad ATD tabulka je operace SEARCH a na ní závislá operace READ, budeme se nejdříve zabývat implementací těchto operací v BVS. Strom je typická dynamická datová struktura, a proto v dalších odstavcích rozebereme take operace INSERT a DELETE.

3.1. Vyhledávání v BVS – operace SEARCH

Nechť jsou dány typy:


	TYPUKUZEL = ^TYPUZEL;
	TYPUZEL = record
			DATA:TYPDATA;
			KLIC:TYPKLIC;
			LEVY, PRAVY: TYPUKUZEL
		       end;

Nechť je dále dán binární vyhledávací strom, jehož uzly jsou typu TYPUZEL a nechť je dána proměnná KOREN typu TYPUKUZEL, která ukazuje na kořen tohoto stromu. Algoritmus vyhledání uzlu stromu, jehož složka KLIC je shodná s vyhledávaným klíčem K typu TYPKLIC lze zapsat s pomocí rekurze nebo nerekurzivně.

3.1.1. Rekurzivní zápis vyhledávání

Princip rekurzivního zápisu algoritmu vyhledávání ve stromu je založen na rekurzivnosti datové struktury strom. Algoritmus má tvar:


	function SEARCH (KOREN:TYPUKUZEL; K:TYPKLIC):Boolean;
	{ Funkce má hodnotu true, jestliže v BVS daném ukazatelem KOREN existuje uzel, jehož 
  složka KLIC se rovná vyhledávanému klíči K }
begin
	if KOREN <> nil
		then if KOREN^.KLIC > K
			then SEARCH := SEARCH (KOREN^.LEVY, K) 
				{ hledej v levém podstromu }
			else SEARCH := SEARCH (KOREN^.PRAVY, K)
				{ hledej v pravém podstromu }
		else SEARCH := false;	{ cesta končí v listu – neúspěšné vyhledání }
end	{ funkce SEARCH }

Tato funkce odpovídá specifikaci operace SEARCH tak, jak byla uvedena v předcházející kapitole. Při praktické práci s ATD tabulka je však často vhodný takový druh operace, jehož vedlejším účinkem je v případě úspěšného vyhledávání lokalizace a zpřístupnění nalezeného prvku. Tohoto účinku se může využít k implementaci operace READ. Nechceme-li použít další parametr (např. parametr var KDE:TYPUKUZEL) pro zpřístupnění nalezeného prvku, můžeme pro tento účel použít parametr KOREN s vědomím, že procedura změní jeho původní hodnotu. Protože funkce s výstupními parametry odporuje zásadám správného programování, bude mít operace tvar procedury:


procedure SEARCHTREE (var KOREN:TYPUKUZEL; K:TYPKLIC);
{ Procedura hledá uzel s klíčem K ve stromu zadaném ukazatelem KOREN;
  najde-li uzel, zpřístupní ho ukazatelem KOREN, nenajde-li uzel, bude mít ukazatel 
  KOREN hodnotu nil }
begin
if KOREN <> nil
	then if KOREN^.KLIC <> K
		then begin
			if KOREN^.KLIC < K
				then KOREN := KOREN^.LEVY
				else KOREN := KOREN^.PRAVY;
			SEARCHTREE (KOREN, K)
		        end
end	{ procedury SEARCHTREE }

Po vyvolání procedury SEARCHTREE lze ustavit hodnotu Booleovské proměnné SEARCH na základě hodnoty parametru KOREN takto:


	SEARCH := KOREN <> nil;
	{ if SEARCH then ukazatel KOREN zpřístupňuje nalezený prvek tabulky }

Korektnější podoba této procedury bude mít čtyři parametry a tedy hlavičku:

	procedure SEARCHTREE (KOREN:TYPUKUZEL; K:TYPKLIC; var SEARCH:Boolean;
				     var KDE:TYPUKUZEL);

3.1.2. Nerekurzivní zápis vyhledávání

Nerekurzivní zápis algoritmu operace SEARCH má tvar:


	function SEARCH (KOREN:TYPUKUZEL; K:TYPKLIC):Boolean;
	var KONEC:Boolean;	{ pomocná proměnná pro řízení cyklu }
	begin
		SEARCH := false;
		KONEC := KOREN = nil;
		while not KONEC do
			begin
				if KOREN^.KLIC = K
					then begin	
KONEC := true; { úspěšné vyhledávání }
						SEARCH := true
					        end
					else if KOREN^.KLIC > K
						then KOREN := KOREN^.LEVY
							{ pokračuj v levém podstromu }
						else KOREN := KOREN^.PRAVY;
							{ pokračuj v pravém podstromu }
				if KOREN = nil then KONEC := true
			end
	end	{ procedury SEARCH }

Odvození nerekurzivního zápisu algoritmu operace SEARCHTREE, která v případě úspěšného vyhledání zpřístupní nalezený uzel stromu ponecháme čtenářům.

3.2. Vkládání prvku do BVS – operace INSERT

Podle sémantické definice přepíše operace INSERT hodnotu prvku, který byl v tabulce nalezen. Nebyl-li v tabulce nalezen prvek s daným klíčem, operace INSERT vloží do tabulky nový prvek v tímto klíčem. Je-li tabulka implementována binárním vyhledávacím stromem, končí neúspěšné vyhledávání právě na tom listu stromu, na který se má připojit nový prvek. Je-li neúspěšně vyhledaný klíč menší než klíč listu, připojí se nový uzel vlevo k danému listu, je-li větší, připojí se vpravo k danému listu. Algoritmus operace INSERT může mít rekurzivní i nerekurzivní zápis.

3.2.1. Rekurzivní zápis operace INSERT

Rekurzivní zápis procedury INSERT je velice krátký a přehledný:


procedure INSERT (var KOREN:TYPUKUZEL; K:TYPKLIC; DATAUZLU:TYPDATA);
{ procedura vloží do stromu prvek s klíčem K a se složkou DATAUZLU }
begin
	if KOREN = nil
		then	{ Prvek s klíčem K není prvkem stromu; vloží se nový prvek }
			begin
				new (KOREN);
				with KOREN^ do begin
{ Ustavení hodnoty, klíče a ukazatelů nového uzlu }
	KLIC := K;
	LEVY := ni;‘
	PRAVY := ni;
	DATA := DATAUZLU;
end
				end
			else	{ Zatím se hledaný uzel nenašel a ještě se nedošlo k listu }
				if K < KOREN^.KLIC
					then	{ Pokračuj v levém podstromu }
						INSERT (KOREN^.LEVY, K, DATAUZLU)
					else
						if K > KOREN^.KLIC
						then	{ Pokračuj v pravém podstromu }
							INSERT (KOREN^.PRAVY, K, DATAUZLU)
						else	{ Uzel s klíčem K je prvkem stromu,
							  jeho datová složka se přepíše }
							KOREN^.DATA := DATAUZLU
	end 	{ provedury INSERT }
	
 

3.2.2. Nerekurzivní zápis operace INSERT

Nerekurzivní zápis procedury INSERT je podstatně delší a vyžaduje samostatný mechanismus vyhledání.


procedure INSERT (var KOREN:TYPUKUZEL; k:TYPKLIC; DATAUZLU:TYPDATA);
var POMUK, KDE:TYPUKUZEL;	{ Pomocné proměnné pro příkaz new a pro 
  lokalizaci místa vkládání }
 	     NASEL:Boolean;	{ Řídící proměnná cyklu }
	
	procedure INSERTSEARCH (KOREN:TYPUKUZEL; K:TYPKLIC; var NASEL:
				         Boolean; var KDE:TYPUKUZEL);
	{ Tělo procedury bude rozvedeno později. Procedura vyhledává v BVS prvek
	  s klíčem K. Nelezne-li jej, pak NASEL=true a KDE zpřístupňuje nalezený prvek.
	  Jinak je NASEL=false a KDE zpřístupňuje list, na který se připojí nový uzel. }

begin	{ Začátek těla procedury INSERT }
	INSERTSEARCH (KOREN, K, NASEL, KDE);	{ Vyhledání }
	if NASEL
		then KDE^.DATA := DATAUZLU { Přepis datové složky nalezeného uzlu }
		else { Prvek nebyl nalezen, bude se vkládat nový uzel }
			begin
				new (POMUK);
				with POMUK^ do { Ustavení hodnot složek nového uzlu }
					begin
						DATA := DATAUZLU;
						KLIC := K;
						LEVY := nil;
						PRAVY := nil;
					end;
				if KDE = nil
					then KOREN := POMUK { Strom byl prázdný; nový 
    uzel se stane kořenem }
					else { Strom byl neprázdný, uzel se připojí k listu }
						if KDE^.KLIC > K
							then KDE^.LEVY := POMUK
							{ Připojení uzlu k listu vlevo }
							else KDE^.PRAVY := POMUK
							{ Připojení uzlu k listu vpravo }
			end { příkazu if NASEL }
end; { procedury INSERT }

Procedura vyhledávání za účelem vkládání uzlu má tvar:


procedure INSERTSEARCH (KOREN:TYPUKUZEL; K:TYPKLIC; var NASEL:Boolean;
			         var KDE:TYPUKUZEL);
var KONEC:Boolean;	{ Pomocná proměnná pro řízení cyklu }
begin
	NASEL := false;
	if KOREN = nil
	then 	{ Strom je prázdný }
		KDE := nil
	else	{ Strom je neprázdný }
		begin
			KONEC := false;
			while not KONEC do
			begin
				KDE := KOREN
				if KOREN^.KLIC = K
				then { Úspěšné vyhledání }
					begin
						NASEL := true
						KONEC := true
					end
				else begin
					if KOREN^.KLIC > K
					then KOREN := KOREN^.LEVY
					else KOREN := KOREN^.PRAVY;
					KONEC := KOREN = nil;
				        end
			end { cyklu while }
		end
end { procedury INSERTSEARCH }

3.3. Rušení prvku v BVS - operace DELETE

U všech předcházejících implementačních metod ATD tabulka bylo vždy rušení prvku v tabulce obtižnější než vkládání. Binární vyhledávací strom je typický svou vhodností pro implementaci dynamické tabulky. Přesto je vyřazování uzlu ze stromu (operace DELETE) složitější, než vkládání nového uzlu (operace INSERT). Jeden z možných postupů při vyřazování uzlu, na němž je „levá“ a „pravá“ varianta rušení uzlů.

Mechanizmus tohoto způsobu rušení lze popsat slovně takto:

Rušíme-li neterminální uzel, který má „synovské uzly“, pak levý synovský uzel připojíme k nadřazenému (praotcovskému) uzlu místo rušeného (otcovského) uzlu a pravý synovský uzel připojíme k nejpravějšímu listu podstromu levého synovského uzlu (tzv. „levá“ varianta) nebo provedeme stranově sdruženou variantu („pravá“ varianta). Je-li levý nebo pravý synovský prázdný, pak se situace zjednodušší: na místo rušeného otcovského uzlu se připojí neprázdný synovský podstrom. Ruší-li se kořen, stane se kořenem levý (resp. pravý) synovský uzel a pravý (resp. levý) podstrom se připojí k nejpravějšímu (resp. nejlevějšímu) listu levého (resp. pravého) podstromu. Vyřazovaný prvek se zruší operací typu DISPOSE.

Vážnou nevýhodou tohoto řešení je skutečnost, že rušením uzlů (zvláště těch, které jsou blízko kořenu) se zvyšuje počet úrovní (výška) stromu. Tím se prodlužuje cesta k listům stromu, a tudíž i proces vyhledávání. Nebudeme proto tuto metodu dále rozvíjet. Z cvičných důvodů lze doporučit vytvoření procedury DELETE pracující podle této metody.

Výhodnější řešení nabízí mechanismus, vycházející z této úvahy: rušení listu a uzlu, jehož jeden z podstromů je prázdný, je velmi jednoduché. Položme si otázku, zda existuje list nebo uzel jen s jedním synem, jehož hodnotu lze vložit do rušeného uzlu (přepsat hodnotu rušeného uzlu), takový, že strom, který vznikne tímto přepisem a zrušením takového listu (nebo uzlu jen s jedním synem), splňuje podmínky BVS? Takovým uzlem je nejpravější uzel levého podstromu „rušeného“ uzlu. I zde lze vytvořit stranově souměrnou variantu „nejlevějšího uzlu pravého podstromu“.

3.3.1. Rekurzivní zápis operace INSERT

Rekurzivní zápis operace DELETE je, podobně jako v předcházejícím případech, kratší a elegantnější než jeho nerekurzivní podoba. Rekurzivní zápis je však obtížnější a nelze očekávat, že by takový algoritmus samostatně vytvořil nezkušený programátor.


procedura DELETE (var KOREN:TYPUKUZEL; K:TYPKLIC);
{ Procedura DELETE vyhledá ve stromu zadaném ukazatelem na kořen (KOREN) uzel s klíčem K a zruší tento uzel druhou z metod uvedených v předcházejícím odstavci . Nenajde-li se uzel s klíčem K, neprovede procedura žádné změny nad stromem a na tuto situaci dále nereaguje. Případnou reakci lze vložit na komentářem vyznačené místo. }
var POMUK:TYPUKUZEL;	{ Pomocný ukazatel na rušený uzel }

procedura DEL (var UK:TYPUKUZEL);
{ Pomocná procedura DEL prochází po nejpravější větvi levého podstromu vyřazovaného uzlu (POMUK) a hledá nejpravější uzel (UK). Po jeho nalezení přepíše jeho hodnotu datovou složkou a klíč uzlu POMUK a uvolní uzel UK tak, aby po skončení procedury mohl být zrušen příkazem dispose. }
begin
	if UK^.PRAVY <> nil
	then DEL (UK^.PRAVY)	{ hledá dále v pravém podstromu }
	else	{ Nalezl nejpravější, provede přepis a uvolnění uzlu UK }
		begin
			POMUK^.KLIC := UK^.KLIC;	{ Přepis složky KLIC }
			POMUK^.DATA := UK^.DATA;	{ Přepis složky DATA }
			POMUK := UK;
			UK := UK^.LEVY;	{ Uvolnění uzlu UK^. Pozor! UK je
			v proceduře DEL ukazatelová složka nadřazeného uzlu
			k uzlu UK^ }
		end
end;	{ Pomocné procedury DEL }

	begin { Začátek procedury DELETE }
	if KOREN <> nil
	then { Hledání není u konce; hledaný prvek ještě může být ve stromu }
		if K < KOREN^.KLIC
		then DELETE (KOREN^.LEVY, K)	{ Hledej v levem podstromu }
		else if K < KOREN^.KLIC
			then DELETE (KOREN^.PRAVY, K)	{ Hledej v pravém podstromu }
			else { hodnota uzlu KOREN^ se ruší }
				begin
					POMUK := KOREN;
					if POMUK^.PRAVY = nil
					then { Nemá pravý podstrom; uvolní se tím, že se levý
						podstrom připojí na nadřazený uzel. }
						KOREN := POMUK^.LEVY
					else { Má pravý podstrom; uzel se bude přepisovat
						nejpravějším v levém podstromu procedurou
						DEL,  je-li levý strom neprázdný, připojí se 
pravý podstrom na nadřazený uzel. }
if POMUK^.LEVY = nil
then KOREN := POMUK^.PRAVY;
	{ Připojení pravého podstromu }
else DEL (POMUK^.LEVY);
						dispose (POMUK)	{ Zrušení uvolněného uzlu }
					end { za else na tomto místě lze reagovat na nenalezení prvku
						ve stromu }
	end { Procedury DELETE }
Zdroj: Petr Pindur - vyhledávání

×Odeslání článku na tvůj Kindle

Zadej svůj Kindle e-mail a my ti pošleme článek na tvůj Kindle.
Musíš mít povolený příjem obsahu do svého Kindle z naší e-mailové adresy kindle@programujte.com.

E-mailová adresa (např. novak@kindle.com):

TIP: Pokud chceš dostávat naše články každé ráno do svého Kindle, koukni do sekce Články do Kindle.

1 názor  —  1 nový  
Hlasování bylo ukončeno    
0 hlasů
Google
(fotka) Lukáš ChurýLukáš je šéfredaktorem Programujte, vyvíjí webové aplikace, fascinuje ho umělá inteligence a je lektorem na FI MUNI, kde učí navrhovat studenty GUI. Poslední dobou se snaží posunout Laser Game o stupeň výše a vyvíjí pro něj nové herní aplikace a elektroniku.
Web     Twitter     Facebook     LinkedIn    

Nové články

Obrázek ke článku Hybridní inteligentní systémy 2

Hybridní inteligentní systémy 2

V technické praxi využíváme často kombinaci různých disciplín umělé inteligence a klasických výpočtů. Takovým systémům říkáme hybridní systémy. V tomto článku se zmíním o určitém typu hybridního systému, který je užitečný ve velmi složitých výrobních procesech.

Obrázek ke článku Jak vést kvalitně tým v IT oboru: Naprogramujte si ty správné manažerské kvality

Jak vést kvalitně tým v IT oboru: Naprogramujte si ty správné manažerské kvality

Vedení týmu v oboru informačních technologií se nijak zvlášť neliší od jiných oborů. Přesto však IT manažeři čelí výzvě v podobě velmi rychlého rozvoje a tím i rostoucími nároky na své lidi. Udržet pozornost, motivaci a efektivitu týmu vyžaduje opravdu pevné manažerské základy a zároveň otevřenost a flexibilitu pro stále nové výzvy.

Obrázek ke článku Síla týmů se na home office může vytrácet. Odborníci radí, jak z pracovních omezení vytěžit maximum

Síla týmů se na home office může vytrácet. Odborníci radí, jak z pracovních omezení vytěžit maximum

Za poslední rok se podoba práce zaměstnanců změnila k nepoznání. Především plošné zavedení home office, které mělo být zpočátku jen dočasným opatřením, je pro mnohé už více než rok každodenní realitou. Co ale dělat, když se při práci z domova ztrácí motivace, zaměstnanci přestávají komunikovat a dříve fungující tým se rozpadá na skupinu solitérů? Odborníci na personalistiku dali dohromady několik rad, jak udržet tým v chodu, i když pracovní podmínky nejsou ideální.

Reklama autora

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032024 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý