Předmět: Algoritmizace praktické aplikace (3ALGA) Vytvořil: Jan Brzeska Zadání: Vytvoření funkcí na stromech (reprezentace stromu směrníky). Zadané funkce: 1. Počet vrcholů 2. Počet listů 3. Součet 4. Hloubka (plytkost) 5. Šířka Popis jen principu, jak funguje algoritmus a ne konkrétní proměnné a každý příkaz. Pro reprezentaci stromu pomocí směrníků je potřeba nejprve vytvořit datový typ, který bude zastupovat jednotlivé vrcholy binárního stromu. Nejvhodnějším typem bude typ záznam: type vrchol = record klic: Integer; pocet: Integer; levy, pravy: TStrom; První položka záznamu, proměnná klic reprezentuje hodnotu umístěnou do tohoto vrcholu stromu. Jelikož je strom uspořádaný (zleva doprava) má každá hodnota své přesné místo ve vrcholu nebo listu stromu a nesní se již v jiném místě stromu opakovat. Proměnná pocet proto udává četnost, neboli počet výskytů dané hodnoty ve stromě, pokud se ve vstupních hodnotách objevuje vícekrát. Poslední dvě položky záznamu levy, pravy reprezentují odkazy na další vrcholy nebo listy stromu a jsou typu směrník, který ukazuje na místo v paměti, kde se vyskytuje další záznam (vrchol) stromu. type TStrom = ^vrchol; Tímto směrníkovým způsobem je zajištěno spojení jednotlivých vrcholů stromu od kořenů až po listy. Abychom ale vždy věděli ve kterém vrcholu strom začíná je nutné vytvořit ještě proměnnou St typu TStrom, která bude vždy obsahovat odkaz na kořen (začátek) stromu. var St: TStrom; 1. Implementace funkce počet vrcholů Tato funkce má za úkol spočítat počet vrcholů v našem již existujícím směrníkovém stromu. function pocetvrcholu(s: TStrom): Integer; result:=1; if S^.levy<>nil then result:=result+pocetvrcholu(s^.levy); if S^.pravy<>nil then result:=result+pocetvrcholu(s^.pravy);
Vstupní proměnná je typu TStrom, což je ukazatel na vrchol stromu, aby funkce fungovala správně je pak v programu při jejím volání potřeba zadat odkaz na kořen stromu, tedy proměnnou St. Funkce vrací hodnotu udávající počet vrcholů ve stromě. Funkce funguje rekurzivně, to znamená, že uvnitř volá sama sebe. Tak dochází k stále hlubšímu vnořování se do struktury stromu. Když funkce narazí na konec stromu opět se začíná vynořovat zpět a dochází ke sčítání počtu vrcholů. Funkce tedy nejprve zjišťuje zdali je vrchol, pro který je zavolána prázdný (koncový) nebo ne. Podle toho pak nastaví hodnotu výsledku 0 nebo 1. result:=1; V případě nalezení konce stromu pouze vrací 0 a končí, pokud ale vrchol stromu existuje nastaví hodnotu na 1 a dále pak kontroluje zda tento vrchol stromu má další listy, resp. odkazy na další vrcholy stromu v paměti, to znamená že proměnné levy a pravy odkazující na další vrcholy nejsou prázdné. Nejprve se kontroluje levá větev a pokud odkaz není prázdný znamená to že strom pokračuje i doleva. Proto se opět rekurzivně volá stejná funkce pocetvrcholu pro další z vrcholů stromu. A po návratu je zjištěná hodnota přičtena k výsledku. Počet vrcholů tak narůstá. if S^.levy<>nil then result:=result+pocetvrcholu(s^.levy); Stejný postup se pak opakuje i pro pravou větev a dochází tak k větvení na obě strany, čímž je zajištěna úplnost výsledku. if S^.pravy<>nil then result:=result+pocetvrcholu(s^.pravy); Funkce tedy volá sama sebe tolikrát, kolik je ve stromě vrcholů. Díky rekurzi je algoritmus velmi jednoduchý a univerzální, jeho nároky na paměť jsou však větší. 2. Implementace funkce počet listů Tato funkce má za úkol zjistit počet listů ve stromu, což jsou vlastně vrcholy, které již nemají další větve. Nemají tedy odkazy na levý a pravý vrchol, který by strom dále zvětšoval. Vstupní proměnná je typu TStrom, stejně jako v předchozím případě a pokud chceme prohledat celý strom při jejím volání v programu opět používáme na vstupu proměnnou St, kořen stromu. Funkce vrací počet listů ve stromu. function pocetlistu(s: TStrom): Integer; if (S^.levy=nil) and (S^.pravy=nil) then result:=1 IF s^.levy<>nil then result:=result+pocetlistu(s^.levy); IF s^.pravy<>nil then result:=result+pocetlistu(s^.pravy); Funkce je opět rekurzivní a ve své podstatě velmi podobná předchozí funkci pro zjištění počtu všech vrcholů (i konečných = listů) stromu. Na začátku funkce je podmínka, která zjišťuje zda vstupní vrchol je listem či nikoliv. Tedy pokud je levý i pravý odkaz na další vrcholy stromu prázdný, jedná se o list a funkce přímo vrací hodnotu 1. Znamená to, že narazila na konec stromu. if (S^.levy=nil) and (S^.pravy=nil) then result:=1
Pokud ale zkoumaný vstupní vrchol má alespoň jeden odkaz na levý nebo pravý následující vrchol, je třeba aby opět rekurzivně zavolala sama sebe a pokračovala ve zkoumání hlouběji ve stromě. Nejprve však nastaví svou návratovou hodnotu na nula, protože není listem. Dále pak podle toho ve které z větví se nachází další vrchol se vnořuje buď do levé pravé nebo obou částí stromu voláním sama sebe. A při návratu přičítá k dosavadní vlastní zjištěné hodnotě 0 hodnotu výsledku následně volaných instancí funkce. Tím opět dojde k předávání a přičítání hodnot zpětně mezi funkcemi při vynořování. If s^.levy<>nil then result:=result+pocetlistu(s^.levy); If s^.pravy<>nil then result:=result+pocetlistu(s^.pravy); Funkce tedy volá sama sebe tak dlouho, dokud nenarazí na list, jedině ten vrací hodnotu 1, která se zpětně předává a přičítá. Pak dojde k vynořování zpět až k první instanci volané funkce, která vrací kompletní výsledek. 3. Implementace funkce počet listů Funkce vrací součet všech hodnot obsažených ve stromě, v našem případě je to součet proměnných klic které jsou v záznamech jednotlivých vrcholů stromu. Její implementace formou rekurze je velmi jednoduchá. Vstupní proměnná je stejného typu jako v předchozích případech a platí pro ni stejná pravidla. function soucet(s: TStrom): Integer; result:=soucet(s^.levy)+s^.klic+soucet(s^.pravy) Princip rekurze je opět stejný, funkce volá stále opakovaně další instance stejné funkce pro další vrcholy stromu, dokud nenarazí na prázdný vrchol. První podmínka tedy zjišťuje zda se jedná o prázdný nebo existující vrchol. V případě prázdného vrcholu funkce dosadí návratovou hodnotu 0 (prázdný, neexistující vrchol nemá hodnotu) a postupně se vynořuje. Pokud ale vrchol existuje dochází k výpočtu hodnoty a to sečtením hodnoty klíče tohoto vrcholu a vrcholů v levé a pravé následující větvi. Proto funkce volá rekurzivně sama sebe aby zjistila hodnoty v dalších následujících vrcholech. Dochází tak k vnořování. Při vynořování se pak hodnoty sčítají a předávají předchozím instancím funkce kde se opět sčítají a tak pořád dokola dokud nedojde k úplnému návratu ke kořeni stromu s celkovým výsledkem. 4. Implementace funkce hloubka (plytkost) Tato funkce má za úkol zjistit největší a nejmenší hloubku stromu. To znamená největší počet větví (úrovní) od kořene k (nejvzdálenějšímu) listu a nejmenší počet větví od kořene k (nejbližšímu) listu. Abychom mohli jednodušeji provést výpočet je dobré vytvořit pomocné funkce, které nám pomohou zjistit maximu a minimum ze dvou zadaných čísel. function max(a, b: Integer): Integer;
if a<b then result:=b result:=a function min(a, b: Integer): Integer; if a<b then result:=a result:=b Samotná funkce hloubka pak rekurzivně, zpětně porovnává a sčítá jednotlivé hloubky ve vrcholech a to tak, že porovnává dosavadně zjištěnou hloubku v levé a pravé větvi. Ta která je větší se předá instanci funkce která pracuje s nadřazeným vrcholem a přičte se k ní hodnota 1 (protože se vzdálenost od kořene zmenšila o další úroveň). Dochází opět k porovnávání. Poslední porovnání se provádí v kořenu stromu, kde se stanoví jednoznačný výsledek. function hloubka(s: TStrom): Integer; if S=nil then result:=-1 result:=max(hloubka(s^.levy), hloubka(s^.pravy))+1 Pokud je vstupní vrchol se kterým má funkce pracovat prázdný, znamená to, že je to konec stromu a vrchol již neexistuje, funkce vrací hodnotu -1. if S=nil then result:=-1 pokud ovšem vrchol existuje, funkce volá sama sebe pro vrcholy v levé a pravé větvi a to přímo jako vstupní parametry pomocné funkce max, která zjistí ve které z větví je počet úrovní větší. Tak dochází k vnořování do struktury stromu. Větev s větším počtem doposud zjištěných úrovní je pak vyhodnocena jako max a po přičtení hodnoty 1 je výsledek předán instanci funkce v nadřazeném vrcholu. Tak dochází k vynořování. result:=max(hloubka(s^.levy), hloubka(s^.pravy))+1 Funkce pro zjištění plytkosti je podobná jen s tím rozdílem, že zde dochází ke zjišťování a porovnávání minima, čili minimální hloubky ve stromě. Ve vrcholu se vždy vybere zjištěná menší hodnota počtů úrovní v levé a pravé větvi. function plytkost(s: TStrom): Integer; if S=nil then result:=-1 result:=min(plytkost(s^.levy), plytkost(s^.pravy))+1 5. Implementace funkce šířka Funkce šířka zjistí největší šířku stromu, to je největší počet prvků (vrcholů i listů) vedle sebe ve stejné úrovni stromu. Samotná funkce sirka není rekurzivní ale pro svou funkci využívá dvě rekurzivní funkce a to již dříve popsanou funkci hloubka a novou funkci pocetnaurovni. Nejprve tedy vytvoříme novou rekurzivní funkci pocetnaurovni, která má za úkol zjistit šířku stromu v zadané úrovni. Na vstupu bude tedy kromě proměnné obsahující první vrchol stromu (S) i proměnná udávající úroveň (n) na které probíhá zjišťování šířky. function pocetnaurovni(n: Integer; S: TStrom): Integer;
if n=0 then result:=1 if s^.levy<>nil then result:=result+pocetnaurovni(n-1, S^.levy); if s^.pravy<>nil then result:=result+pocetnaurovni(n-1, S^.pravy); Při prohledávání stromu se vychází od kořene stromu a s každým dalším v nořením se úroveň n snižuje o 1. To znamená, že funkce nejprve několikrát volá sama sebe (vnořuje se) dokud nedosáhne hodnoty n = 0, čili požadované zjišťované úrovně. Pak teprve vrací hodnotu 1, která indikuje že se ocitla v hledané úrovni a pracuje právě s jedním z vrcholů, který nás zajímá. Poté se začíná vynořovat. if n=0 then result:=1 Pokud ovšem ještě nedosáhla požadované úrovně, a v levé nebo pravé větvi existuje směrník na další podvrchol stromu, volá opět sama sebe, resp. další instanci funkce s odkazem na tento podvrchol a s n-1, jelikož je opět o úroveň hlouběji. Výsledek (result) v této úrovni nejprve nastavuje na 0. if s^.levy<>nil then result:=result+pocetnaurovni(n-1, S^.levy); if s^.pravy<>nil then result:=result+pocetnaurovni(n-1, S^.pravy); Při vynořování pak opět dochází ke sčítání výsledků instancí funkcí z jednotlivých hlubších úrovní a při úplném návratu ke kořeni stromu je vrácená hodnota úplná. Funkce sirka pak pomocí funkce hloubka nejprve zjistí počet úrovní daného stromu a pomocí cyklu (for i) prohledává jednotlivé úrovně stromu od nulté až po poslední (hloubka(s)), přičemž hledá maximální šířku stromu. Výsledek funkce se nejprve nastaví na 0 (result), to znamená že max. šířka stromu je na začátku 0. Pak se porovnává se zjištěnou šířkou dané úrovně (funkce pocetnaurovni), která je průběžně ukládána do proměnné p. Pokud je p větší, výsledek se nastaví na větší zjištěnou šířku stromu. function sirka(s: TStrom): Integer; var i, p: Integer; for i:=0 to hloubka(s) do p:=pocetnaurovni(i, S); if p>result then result:=p Po průchodu všemi úrovněmi stromu, je v p maximální zjištěná šířka stromu. Celý program implementation {$R *.dfm}
type TStrom = ^vrchol; vrchol = record klic: Integer; pocet: Integer; levy, pravy: TStrom; var St: TStrom; function pocetvrcholu(s: TStrom): Integer; result:=1; if S^.levy<>nil then result:=result+pocetvrcholu(s^.levy); if S^.pravy<>nil then result:=result+pocetvrcholu(s^.pravy); function pocetlistu(s: TStrom): Integer; if (S^.levy=nil) and (S^.pravy=nil) then result:=1 IF s^.levy<>nil then result:=result+pocetlistu(s^.levy); IF s^.pravy<>nil then result:=result+pocetlistu(s^.pravy); function soucet(s: TStrom): Integer; result:=soucet(s^.levy)+s^.klic+soucet(s^.pravy) function soucetnerekurz(s: TStrom): Integer; var zas: array [1..20] of TStrom; vrch: Integer; vrch:=1; zas[vrch]:=s; repeat if zas[vrch]=nil then Dec(vrch) S:=zas[vrch]; result:=result+s^.klic; zas[vrch]:=s^.levy; Inc(vrch); zas[vrch]:=s^.pravy until vrch=0 function max(a, b: Integer): Integer; if a<b then result:=b result:=a
function hloubka(s: TStrom): Integer; if S=nil then result:=-1 result:=max(hloubka(s^.levy), hloubka(s^.pravy))+1 function min(a, b: Integer): Integer; if a<b then result:=a result:=b function plytkost(s: TStrom): Integer; if S=nil then result:=-1 result:=min(plytkost(s^.levy), plytkost(s^.pravy))+1 function pocetnaurovni(n: Integer; S: TStrom): Integer; if n=0 then result:=1 if s^.levy<>nil then result:=result+pocetnaurovni(n-1, S^.levy); if s^.pravy<>nil then result:=result+pocetnaurovni(n-1, S^.pravy); function sirka(s: TStrom): Integer; var i, p: Integer; for i:=0 to hloubka(s) do p:=pocetnaurovni(i, S); if p>result then result:=p procedure VytvorBVS(x: Integer; var S: TStrom); if S=nil then new(s); S^.klic:=x; S^.pocet:=1; S^.levy:=nil; S^.pravy:=nil if x<s^.klic then VytvorBVS(x, S^.levy) if x>s^.klic then VytvorBVS(x, S^.pravy) S^.pocet:=S^.pocet+1 procedure TForm2.Button1Click(Ser: TObject); var f: TextFile;
a: Integer; button1.enabled := false; button2.enabled := true; button3.enabled := true; button4.enabled := true; button5.enabled := true; AssignFile(f,Edit2.Text); reset(f); while not EOF(f) do readln(f, a); VytvorBVS(a, St) CloseFile(f) procedure TForm2.Button2Click(Ser: TObject); procedure Tisk(h: Integer; S: TStrom); var s1: String; i: Integer; if S<>nil then Tisk(h+1, S^.pravy); s1:=''; for i:=1 to h do s1:=s1+' '; S1:=s1+IntToStr(S^.klic); Memo1.Font.Color:=clRed; Memo1.Lines.Add(s1); Tisk(h+1, S^.levy) var i: Integer; Memo1.Lines.Clear; Tisk(1, St); memo2.lines.clear; Memo2.Font.Color:=clGreen; Memo2.Lines.Add('Počet vrcholů: '+IntToStr(pocetvrcholu(St))); Memo2.Lines.Add('Počet listů: '+IntToStr(pocetlistu(St))); Memo2.Lines.Add('Součet vrcholů: '+IntToStr(soucet(St))); Memo2.Lines.Add('Součet vrcholů: '+IntToStr(soucetnerekurz(St))); Memo2.Lines.Add('Hloubka: '+IntToStr(hloubka(St))); Memo2.Lines.Add('Plytkost: '+IntToStr(plytkost(St))); for i:=0 to hloubka(st) do Memo2.Lines.Add('Úroveň '+IntToStr(i)+' má '+'vrcholů: '+IntToStr(pocetnaurovni(i, St))); Memo2.Lines.Add('Šířka: '+IntToStr(sirka(St))); procedure TForm2.Button3Click(Ser: TObject); procedure Vykresli(x, y, sirka: Integer; S: TStrom); sirka:=sirka div 2; if S<> nil then if S^.levy<>nil then Image1.Canvas.MoveTo(x, y);
Image1.Canvas.LineTo(x-sirka, y+30); Vykresli(x-sirka, y+30, sirka, S^.levy) if S^.pravy<>nil then Image1.Canvas.MoveTo(x, y); Image1.Canvas.LineTo(x+sirka, y+30); Vykresli(x+sirka, y+30, sirka, S^.pravy) Image1.Canvas.TextOut(x-5, y-8, IntToStr(S^.klic)) Image1.Canvas.Brush.Color:=clWhite; Image1.Canvas.Brush.Style:=bsSolid; Image1.Canvas.Rectangle(0, 0, Image1.Width, Image1.Height); Image1.Canvas.Pen.Color:=clGray; Image1.Canvas.Brush.Color:=RGB(255,255,150); Vykresli(Image1.Width div 2, 10, Image1.Width div 2, St) procedure TForm2.Button4Click(Ser: TObject); var a: Integer; procedure Zrus(var S: TStrom; x: Integer); var pom: TStrom; function ZrusMin(var S: TStrom): Integer; var pom: TStrom; if S^.levy=nil then result:=s^.klic; pom:=s; S:=S^.pravy; dispose(pom) result:=zrusmin(s^.levy) if S=nil then if x<s^.klic then Zrus(S^.levy, x) if x>s^.klic then Zrus(S^.pravy, x) if (S^.levy=nil) and (S^.pravy=nil) then dispose(s); S:=nil if S^.pravy=nil then pom:=s; S:=S^.levy; dispose(pom) if S^.levy=nil then pom:=s; S:=S^.pravy; dispose(pom) S^.klic:=ZrusMin(S^.pravy) a:=strtoint(edit1.text); Zrus(ST, a); Edit1.Text := ''; Edit1.SetFocus;
procedure TForm2.Button5Click(Ser: TObject); procedure Vymaz(var S: TStrom); button1.enabled := true; button2.enabled := false; button3.enabled := false; button4.enabled := false; button5.enabled := false; if S^.levy<>nil then Vymaz(S^.levy); if S^.pravy<>nil then Vymaz(S^.pravy); Dispose(S); S:=nil Vymaz(St); Edit2.SetFocus;.