310 likes | 459 Vues
10 . Dynamické datové struktury. Statická datová struktura - její rozsah se během provádění programu nemění - pole, záznamy. Dynamická datová struktura - její rozsah se během výpočtu mění. Vytváří se pomocí dynamických proměnných, jejich spojováním do seznamů.
E N D
10. Dynamické datové struktury Statická datová struktura - její rozsah se během provádění programu nemění - pole, záznamy. Dynamická datová struktura - její rozsah se během výpočtu mění. Vytváří se pomocí dynamických proměnných, jejich spojováním do seznamů. Dynamická proměnná je typu záznam a obsahuje jeden jeden nebo více ukazatelu na tento typ záznam. Nejjednodušší dynamickou datovou strukturou je lineární seznam, ve kterém každá dynamická proměnná ukazuje na svého následníka a jedna proměnná typu ukazatel ukazuje na jeho začátek.
Operace nad spojovým seznamem: - přidání prvku na začátek seznamu: prvek^.dalsi:=zacatek; zacatek:=prvek; - odebrání prvního prvku v seznamu: zacatek:=zacatek^.dalsi; - provedení operace pro všechny prvky v seznamu: p:=zacatek; s:=0; while p <> nil do begin s:=s + p^.hodnota; {proveden operace s p^} p:=p^.dalsi; end;
- přidání prvku na konec seznamu: posledni^.dalsi:=prvek; posledni:=prvek; - vložení prvku za označený prvek prvek^.dalsi:=za^.dalsi; za^.dalsi:=prvek;
- vložení prvku před označený prvek Procedura vyhledá prvek předcházející prvek před který chceme vložit a provede vložení za tento prvek. procedure vlozpred(var zacatek:spoj; prvek,pred:spoj); var p:spoj; begin if pred=zacatek then begin prvek^.dalsi:=zacatek; zacatek:=prvek end else begin p:=zacatek; while p^.dalsi<>pred do p:=p^.dalsi; prvek^.dalsi:=pred; p^.dalsi:=prvek; end; end;
Je-li seznam rozsáhlý, vkládáme nový prvek tak, že vyměníme hodnoty nového a označeného prvku a nový prvek pak vložíme za označený: procedure vlozpred(pred,prvek:spoj); var pom:integer; begin pom:=pred.hodnota; pred^.hodnota:=prvek^.hodnota; prvek^.hodnota:=pom; prvek^.dalsi:=pred^.dalsi; pred^.dalsi:=prvek; end;
- odebrání označeného prvku: 1. Najdeme prvek, který v seznamu předchází označený prvek procedure odeber(var zacatek:spoj; co:spoj); var p:spoj; begin if co=zacatek then zacatek:=zacatek^.dalsi else begin p:=zacatek; while p^.dalsi<>co do p:=p^.dalsi; p^.dalsi:=co^.dalsi; end; end;
2. Do označeného prvku přesuneme hodnotu následujícího prvku a tento následující prvek odebereme (nelze pro poslední prvek). procedure odeber(co:spoj); begin if co^.dalsi<>nil then begin co^.hodnota:=co^.dalsi^.hodnota; co^.dalsi:=co^.dalsi^.dalsi; end; end;
Lineární seznam může být spojen ukazateli obousměrně - kromě ukazatele na další prvek obsahuje proměnná typu objekt také ukazatel na předchozí prvek. type spoj=^objekt objekt=record / /zacatek konec hodnota:integer; dalsi,predchozi:spoj; end; Taková datová struktura je vhodná například pro tvorbu textového editoru. Speciálním případem jednosměrně vázaného lineárního seznamu je zásobník nebo fronta.
Zásobník • Zásobníkem rozumíme takovou dynamickou datovou strukturu, z níž se prvky vybírají v opačném pořadí než v jakém se do ní vkládají. Vybíráme tedy vždy poslední vložený prvek (podle toho se zásobníky také nazývají struktury LIFO - z anglického Last-In-First-Out). • Příkladem použití zásobníku je historie v internetových prohlížečích. Když kliknete na zpět, otevře se posledně otevřená stránka. • Pro zásobník můžeme definovat tyto základní operace: • vytvoření prázdného zásobníku • vložení prvku na vrchol zásobníku • odebrání prvku z vrcholu zásobníku • testování prázdnosti zásobníku • Zásobník můžeme v Pascalu implementovat (vytvořit) buď pomocí pole, tedy jako statickou datovou strukturu, nebo pomocí ukazatele, tedy jako dynamickou datovou strukturu.
Implementace pomocí pole: Při této implementaci jsme nuceni omezit maximální velikost zásobníku a překročení této velikosti musíme v programu ošetřit. Také musíme ošetřit opačný stav, kdy chceme ze zásobníku prvek odebrat, ale zásobník je již prázdný. const Maxdelka = N;{N je číslo omezující maximální délka zásobníku} var zasobnik : array[1..Maxdelka] of datovytyp; vrchol : 1..Maxdelka; procedure Vytvor; begin vrchol := 0; end; function Jeprazdny: boolean; begin if vrchol = 0 then Jeprazdny := true; else Jeprazdny := false; end;
procedure Vloz(X: datovytyp); begin if vrchol = Maxdelka then begin {ošetření překročení maximální velikosti} write('Pozor - zasobnik je plny.'); Halt; end; else begin vrchol := vrchol + 1; zasobnik[vrchol] := X; end; end; procedure Odeber(var X: datovytyp); begin if vrchol = 0 then begin {ošetření prázdnosti zásobníku} write('Pozor - zasobnik je prazdny.'); Halt; end; else begin X := zasobnik[vrchol]; vrchol := vrchol - 1; end; end;
Implementace pomocí ukazatele: Pokud budeme chtít implementovat zásobník pomocí ukazatele, nemusíme řešit maximální velikost zásobníku. Ta je dána velikostí té části operační paměti, ve které se vytvářejí dynamické proměnné - hromada (heap). Prvky se vkládají a odebírají na začátku jednosměrně vázaného lineárního spojového seznamu. type spoj = ^objekt objekt = record hodnota: datovytyp; dalsi: spoj; end; var zasobnik : spoj; procedure Vytvor; begin zasobnik := nil; end; function Jeprazdny: boolean; begin if zasobnik = nil then Jeprazdny := true; else Jeprazdny := false; end;
procedure Vloz(X: datovytyp); var pom: spoj; begin new(pom); with pom^ do begin hodnota := X; dalsi := zasobnik; end; zasobnik := pom; end; procedure Odeber(var X: datovytyp); var pom: spoj; begin if zasobnik = nil then begin write('Pozor - zasobnik je prazdny.'); Halt; end; else begin X := zasobnik^.hodnota; pom := zasobnik; zasobnik := zasobnik^.dalsi; dispose(pom); end; end;
Fronta • Fronta je dynamická datová struktura podobná zásobníku, rozdíl je pouze v tom, že prvky se z fronty odebírají v tom pořadí, v jakém se do fronty vkládají. Jako příklad si můžeme představit frontu nakupujících v obchodě. Podle toho se datový typ fronta také nazývá struktura FIFO - z anglického First-In-First-Out. Pro frontu můžeme definovat tyto operace: • vytvoření prázdné fronty • vložení prvku na konec fronty • odebrání prvku ze začátku fronty • test prázdnosti fronty • Frontu stejně jako zásobník můžeme implementovat buď pomocí pole, nebo pomocí ukazatele.
Implementace pomocí pole: Při této implementaci musíme neustále vědět, kde fronta začíná, kde končí a kolik má prvků. Také co se týče velikosti, musíme omezit maximální velikost fronty a překročení této velikosti musíme ošetřit. Pokud budeme vkládat prvky do prázdné fronty, bude situace vypadat následovně: Vidíme, že do fronty byly postupně vloženy hodnoty a až f.
. Pokud budeme s frontou pracovat tak, že staré prvky z ní budeme podle potřeby vybírat, nové do ní budeme vkládat, zjistíme, že pro nové vložení nám nemusí zbýt políčko ve frontě, zatímco po odebraných prvcích ze začátku fronty nám zbývají volná a nevyužitá políčka. Situace může vypadat např. takto: Takovýto problém můžeme vyřešit např. tím, že po každém výběru prvku ze začátku fronty můžeme celou frontu přesypat na začátek, neboli všechny prvky budeme přesunovat o jedno místo dopředu.
Jednodušším řešením, které můžeme přirovnat k předchozímu řešení, je použití tzv. kruhové fronty. Za následníka posledního prvku považujeme u takovéto kruhové fronty první prvek. Jak je vidět z obrázku, prvním prvkem je zde prvek s indexem 0 a poslední prvek má index N-1 vzhledem k tomu, že přidávání a odebírání prvků do a z fronty, resp. výpočet indexu prvku, je realizováno pomocí operace modulo N.
V následujícím výpisu jsou procedury vytvoření prázdné fronty, přidání prvku do fronty, odebrání prvku z fronty a funkce testování prázdnosti fronty. const MaxDelka = N; {N je číslo omezující délku fronty} MaxIndex = N-1; var fronta: array[1..MaxDelka] of datovytyp; zacatek, konec: MaxIndex; delka: MaxDelka; procedure Vytvor; begin zacatek := 0; konec := 0; delka := 0; end; function JePrazdna: boolean; begin if delka = 0 then JePrazdna := true else JePrazdna := false; end;
procedure Vloz(X: datovytyp); begin if delka = MaxDelka then begin {ošetření překročení maximální velikosti} write('Pozor - fronta je plna.'); Halt; end; else begin fronta[konec] := X; konec := (konec + 1) mod MaxDelka; delka := delka + 1; end; end; procedure Odeber(var X: datovytyp); begin if delka = 0 then begin {ošetření prázdnosti fronty} write('Pozor - fronta je prazdna.'); Halt; end; else begin X := fronta[zacatek]; zacatek := (zacatek + 1) mod MaxDelka; delka := delka - 1; end; end;
Implementace pomocí ukazatele: Podobně jako u zásobníku, kontrola maximální velikosti fronty odpadá a maximální velikost fronty je dána velikostí hromady, ve které je fronta dynamicky vytvořena. V proceduře Odeber je použita pomocná proměnná pom typu spoj, kterou využijeme k odstranění prvního prvku fronty. type spoj = ^objekt; objekt = record hodnota: datovytyp; dalsi: spoj; end; var zacatek, konec: spoj; procedure Vytvor; begin new(zacatek); konec := zacatek; end;
procedure Vloz(X: datovytyp); begin konec^.hodnota := X; new(konec^.dalsi); konec := konec^.dalsi; end; procedure Odeber(var X: datovytyp); var pom: spoj; begin if zacatek = konec then begin write('Pozor - fronta je prazdna.'); Halt; end else begin X := zacatek^.hodnota; pom := zacatek; zacatek := zacatek^.dalsi; dispose(pom); end; end; function JePrazdna: boolean; begin if zacatek = konec then JePrazdna := true else JePrazdna := false; end;
Binární strom Složitější dynamickou datovou strukturou je binární strom, ve kterém z každého uzlu vycházejí nejvýše dvě hrany. Vytváří se pomocí typu uzel: type spoj=^uzel; uzel=record hodnota:integer; levy,pravy:spoj; end;
Průchod stromem za účelem provedení nějaké operace pro všechny uzly stromu se řeší pomocí rekurze. Existuje šest způsobů průchodu stromem, které se liší v pořadí provedení těchto kroků: a) proveď operaci v uzlu U b) projdi levý podstrom c) projdi pravý podstrom. Praktický význam mají tři způsoby průchodu, které mají vlastní jména: preorder- a),b),c) inorder- b),a),c) postorder - c),a),b)
Průchod stromem za účelem provedení nějaké operace pro všechny uzly stromu se řeší pomocí rekurze. Existuje šest způsobů průchodu stromem, které se liší v pořadí provedení těchto kroků: a) proveď operaci v uzlu U b) projdi levý podstrom c) projdi pravý podstrom. Praktický význam mají tři způsoby průchodu, které mají vlastní jména: preorder- a),b),c) inorder- b),a),c) postorder - c),a),b) Použitím těchto průchodů pro uvedený příklad stromu obdržíme toto poředí uzlů: preorder: E B A D C F H G I inorder: A B C D E F G H I postorder: A C D B G I H F E
Př.: Ukázka rekurzivní procedury pro výpis hodnot všech uzlů v pořadí určeném metodou preorder. procedure vypis(strom:spoj); begin if strom<>nil then begin write(strom^.hodnota); vypis(strom^.levy); vypis(strom^.pravy); end; end; Binární vyhledávací strom - pro každý uzel s hodnotou x platí, že všechny hodnoty v levém podstromu jsou menší a v pravém větší než x. Uzly po seřazení metodou inorder tvoří vzestupně uspořádanou posloupnost.
Př.: Rekurzivní procedura pro zařazení do binárního vyhledávacího stromu. procedure zarad(var strom:spoj; x:typhodnoty); begin if strom=nil then begin new(strom); with strom^ do begin hodnota:=x; levy:=nil; pravy:=nil; end end else with strom^ do if x<hodnota then zarad(levy,x); else zarad(pravy,x); end;
Př.: Nerekurzivní funkce pro vyhledání uzlu se zadanou hodnotou v binárním vyhledávacím stromu. Funkce vrací ukazatel na daný uzel nebo nil. function hledej(strom:spojů x:typhodnoty):spoj; var konec:boolean; begin konec:=false; repeat if strom=nil then konec:=true else with strom^ do if x<hodnota then strom:=levy else if x>hodnota then strom:=pravy else konec:=true until konec; hledej:=strom; end;
Př.: Program pro setřídění souboru čísel pomocí zatřiďování do lineárního dynamického seznamu. program dynamicke_trideni; type spoj = ^objekt; objekt = record hodn : integer; dalsi : spoj; end; var zacatek : spoj;
procedure vytvor_seznam(var zacatek : spoj); var prvek : spoj; procedure zarad_prvek(prvek : spoj; var zacatek : spoj); var za,pom : spoj; begin if prvek^.hodn < zacatek^.hodn then begin prvek^.dalsi := zacatek; zacatek := prvek; end else begin pom := zacatek; repeat za := pom; pom := pom^.dalsi; until (pom = nil)or(prvek^.hodn < pom^.hodn); za^.dalsi := prvek; prvek^.dalsi := pom; end; end;
begin {vytvor_seznam} if not eof then begin new(zacatek); with zacatek^ do begin readln(hodn); dalsi := nil; end; while not eof do begin new(prvek); readln(prvek^.hodn); zarad_prvek(prvek,zacatek); end; end; end; {vytvor_seznam}
procedure tiskni_seznam(zacatek : spoj); var prvek : spoj; pocet : integer; begin if zacatek <> nil then begin write(zacatek^.hodn:5); pocet := 1; prvek := zacatek; while prvek^.dalsi <> nil do begin prvek := prvek^.dalsi; write(prvek^.hodn:5); pocet := pocet + 1; if pocet mod 5 = 0 then writeln end; writeln; end; end; begin vytvor_seznam(zacatek); tiskni_seznam(zacatek); end.