Dynamické štruktúry údajov

 

V predchádzajúcich prednáškach sme sa stretli s viacerými štruktúrami údajov, ktorých veľkosť a umiestnenie v operačnej pamäti bola pevne deklarovaná pred ich použitím. Boli to napríklad polia a záznamy.

Údajovú štruktúru, ktorej rozsah sa počas jej existencie nemení, sa nazýva statická údajová štruktúra.

Pri riešení niektorých úloh však sú vytvárané štruktúry, ktoré menia svoj rozsah, tieto štruktúry budeme nazývať dynamické údajové štruktúry.

Typickým príkladom dynamickej údajovej štruktúry je zoznam prvkov určitého typu, na ktorom je používaná operácia pridávania prvkov a operácia odoberania prvkov.

Znamená to, že zoznam zväčšuje svoju veľkosť. Ak vieme ohraničiť počet prvkov zhora a vieme tiež, že pridávanie sa uskutočňuje na jednom konci zoznamu, tak je možné použiť na reprezentáciu zoznamu pole. Ak však pripustíme pridávanie prvkov aj doprostred zoznamu, potom reprezentácia pomocou poľa je nevýhodná, lebo operácia pridávania prvku bude vyžadovať posun mnohých iných prvkov v poli, aby bolo vytvorené miesto na uloženie nového prvku. Ak by bola povolená operácia vynechávania prvkov, tak by vznikli problémy s dierami v poli.

 

Operácie meniace rozsah dynamickej údajovej štruktúry sú efektívnejšie realizované, ak použijeme na ich reprezentáciu smerníky. Každý prvok zoznamu je uložený v samostatnom objekte, ktorého súčasťou je, okrem hodnoty prvku zoznamu, tiež položka obsahujúca smerník (spoj, referenciu, odkaz) na objekt, v ktorom je uložený ďalší prvok zoznamu. Operácia vloženia spočíva vo vytvorení objektu, ktorý obsahuje hodnotu nového prvku a smerník na objekt, pred ktorý sa nový prvok zaraďuje, a v zmene smerníka v tom objekte, za ktorý sa nový prvok zaraďuje.

Smerníkové reprezentácie dynamických údajových štruktúr sa v jazyku PASCAL vytvárajú pomocou dynamických premenných a hodnôt typu smerník.

 

 

Dynamické premenné a typy smerník

 

Dynamickou premennou budeme rozumieť takú premennú, ktorá nie je zavedená v deklarácii premenných, ale vznikne vykonaním špeciálneho príkazu.

Dynamická premenná nemá pridelený identifikátor, ale identifikuje sa pomocou hodnoty zvláštneho typu, ktorú nazývame smerník a ktorú môžeme považovať za abstrakciu adresy umiestnenia dynamickej premennej v pamäti. Podľa typu dynamických premenných rozlišujeme tiež typy smerníkov. Každý typ smerník je viazaný na jeden typ dynamických premenných, ktorý sa nazýva doménový typ smerníka a je určený v popise typu smerník.

Tento popis má tvar

­ doménový typ

 

Doménovým typom môže byť ľubovoľný typ a v popise typu smerník sa označuje identifikátorom typu.

 

 

Napríklad, ak je v programe definovaný typ MENO, tak pomocou deklarácie

type SMER = ­ MENO;

 

je definovaný typ SMER, ktorého hodnotami sú smerníky identifikujúce dynamické premenné typu MENO.

V popise typu smerník môže byť použitý tiež identifikátor typu, ktorý na danom mieste programu nie je ešte definovaný. Táto výnimka je nutná na to, aby bolo možné definovať typy dynamických premenných, z ktorých sa vytvárajú smerníkové reprezentácie dynamických údajových štruktúr.

Zvláštna hodnota, ktorá neidentifikuje žiadnu dynamickú premennú a ktorá môže byť priradená ľubovoľnej premennej typu smerník, je hodnota označená kľúčovým slovom nil.

Ak priradíme túto hodnotu premennej P príkazom

P:= nil,

tak premenná P neukazuje na žiadnu dynamickú premennú.

 

Ovládanie voľnej pamäte

Premenná typu smerník obsahuje adresu v pamäti dynamickej premennej. Smerník je uložený na dvoch slovách a delí sa na offsetovú a segmentovú časť.

Smerníkovej premennej môžeme priradiť hodnotu troma spôsobmi:

  1. New(...),
  2. operátorom @
  3. @ je unárny operátor, ktorý vezme premennú alebo identifikátor procedúry alebo funkcie za operand a vráti smerník k operandu; môže byť priradený ľubovoľnej smerníkovej premennej.

  4. funkciou Ptr

function Ptr(segment, offset:word): pointer;

 

 

Procedúry a funkcie pre dynamickú alokáciu:

uvoľňuje dynamickú premennú, na ktorú ukazuje premenná p; hodnota smerníka po jej vykonaní zostáva nedefinovaná.

uvoľňuje pamäť stanovenej veľkosti; smerník musí ukazovať na dynamickú premennú, ktorá bola vytvorená procedúrou GetMem; size určuje počet uvoľňovaných bytov.

zakladá novú dynamickú premennú uvedenej veľkosti size (menej ako 65522)a uloží jej adresu do premennej p typu smerník.

zaznamená do premennej p aktuálnu adresu hromady (heap); táto adresa môže byť neskôr využitá procedúrou Release; p môže byť smerník ľubovoľného typu.

výsledkom je veľkosť maximálnej súvislej pamäte v hromade, súhlasí s maximálnou veľkosťou dynamickej premennej, ktorú je možné alokovať.

výsledkom je veľkosť súčtu všetkých voľných blokov pamäte v hromade.

vytvára novú dynamickú premennú a nastavuje smerník na ňu.

vracia hromadu do stavu, pred ktorým bola smerníku p priradená hodnota procedúrou Mark; uvoľňuje všetky premenné vytvorené buď procedúrou New alebo GetMem, ktoré boli vytvorené po priradení hodnoty smerníku p procedúrou Mark.

 

 

Použitie dynamických dátových štruktúr - zoznamy

Spájané zoznamy (linked list)

 

Spájané zoznamy budeme používať najmä ako tréning pre dynamické údajové štruktúry realizované pomocou smerníkov

 

Zoznam pomocou smerníkov na záznam:

type
  prvok = integer;
  PVrchol = ^TVrchol;
  TVrchol = record
    info: prvok;
    next: PVrchol;
  end;

var
  z,p: PVrchol;
begin
  z:=nil;                             { z je prázdny zoznam}
    new(z); z^.info:=1; z^.next:=nil;   { z je jednoprvkový zoznam}
    new(p); p^.info:=2; p^.next:=nil;   { p je jednoprvkový zoznam}
   z^.next:=p;                         { z je dvojprvkový zoznam}
   new(p); p^.info:=3; p^.next:=nil;   { p je jednoprvkový zoznam}
   z^.next^.next:=p;                   { z je trojprvkový zoznam}
end;

Výpis prvkov zoznamu:

 Writeln(z^.info, ' ', z^.next^.info,' 'z^.next^.next^.info);

alebo všeobecne:

  p:=z;
  while p<>nil do begin
    write(p^.info,' ');
    p:=p^.next;
  end;

uvoľnenie prvkov zoznamu - chybné riešenie:

  while z<>nil do begin
    dispose(z);
    z:=z^.next;
  end;

uvoľnenie prvkov zoznamu - správne riešenie

  while z<>nil do begin
    p:=z^.next;
    dispose(z);
    z:=p;
  end;

 

Poznámka:

 

Postupné pridávanie prvkov do zoznamu

Vytvárame zoznam pridávaním

  z:=nil;
  for i:=1 to 20 do begin
    new(p); p^.info:=i; p^.next:=z;
    z:=p;
  end;

 

prvky sa pridávali na začiatok -- v zozname sú v opačnom poradí

pridávanie prvkov na koniec zoznamu -- treba nájsť posledný prvok v zozname a zaň napojiť nový prvok:

  Assign(t,'cisla.txt'); Reset(t);
  z:=nil;
  while not eof(t) do begin
    new(p); read(t,p^.info); p^.next:=nil;
    if z=nil then z:=p
    else begin
      k:=z;
      while k^.next<>nil do k:=k^.next;{ nájde posledný prvok v zozname}
      k^.next:=p;
    end;
  end;
  Close(t);

efektívnejšie riešenie - oplatí sa pamätať si posledný prvok v zozname - nebude ho treba stále hľadať:

  Assign(t,'cisla.txt'); Reset(t);
  z:=nil; k:=nil;
  while not eof(t) do begin
    new(p); read(t,p^.info); p^.next:=nil;
    if z=nil then z:=p
    else k^.next:=p;
    k:=p;
  end;
  Close(t);

niekedy budeme používať with:

  p:=z;
  while p<>nil do
    with p^ do begin
      write(info,' ');
      p:=next;
    end;

 

Operácie na zozname

Zadefinujeme triedu TZoznam, ktorá zabezpečí základné operácie nad zoznamom - bude mať dve stavové premenné: začiatok a koniec zoznamu.

prvky zoznamu budú :

type
  prvok = integer;
  Svrchol = ^Tvrchol;
  TVrchol = record
    info:prvok;
    next:SVrchol;
end;

function Tvrchol_Create(i:prvok; n:TVrchol): SVrchol;
var t: SVrchol;
begin
  New(t};
With t^ do
begin info:=i; next:=n; end;
  Tvrchol_Create:= t;
end;


function Tvrchol_vypis(z: SVrchol): string;
begin
  Result:=' '+Str(z^.info);
end;

Operácie

Var z, k : Svrchol;  {Smerniky na zaciatok a koniec}

{ Na zaciatku v oboch je ulozeny nil }

function Tzoznam_vypis:string;
var
  p:TVrchol;
begin
  Result:=''; p:=z;
  while p<>nil do begin
    Result:=Result+p_vypis(p);
    p:=p^.next;
  end;

end;

procedure Tzoznam_pridajZ(i:prvok);

begin
  z:=Tvrchol_Create(i,z);
  if k=nil then k:=z;
end;

procedure Tzoznam_pridajKon(i:prvok);
var
  p:TVrchol;
begin
  p:=Tvrchol_Create(i,nil);
  if z=nil then z:=p
  else k^.next:=p;
  k:=p;
end;

procedure Tzoznam_vsun(pred: integer; i:prvok);
var
  p,q:TVrchol;
begin
  if pred<2 then pridajZ(i)
  else begin
    q:=z;
    while (q<>nil) and (pred>2) do begin
      dec(pred);
      q:=q^.next;
    end;
    p:=Tvrchol_Create(i,nil);
    if q=nil then k^.next:=p
    else begin
      p^.next:=q^.next;
      q^.next:=p;
    end;
    if p^.next=nil then k:=p;
  end;
end;

 

Topologické triedenie—TOPSORT

 

O topologickom triedení hovoríme v súvislosti s čiastočne usporiadanými množinami, na ktorých je definovaná  relácia čiastočného usporiadania, t.j. takých množín, v ktorých je definované usporiadanie len pre niektoré dvojice prvkov (nemusí byť pre všetky dvojice).

Matematická definícia presne popisuje pojem čiastočne usporiadaná množina. čiastočné usporiadanie množiny S je vo všeobecnosti dané reláciou medzi prvkami množiny S. Pre tri rôzne prvky x, y, z Î S táto relácia spĺňa nasledujúce vlastnosti x Đ y:

 

Príklady čiastočne usporiadaných množín:

 

Z praktických dôvodov sa budeme zaoberať konečnými čiastočne usporiadanými množinami.

čiastočne usporiadané množiny môžeme zobraziť názorne pomocou grafov. Prvky množiny budú uložené vo vrcholoch a pomocou orientovaných hrán vyjadríme smer usporiadania.

Problém topologického triedenia spočíva vo vnorení čiastočného usporiadania do lineárneho usporiadania. Graficky to znamená, že vrcholy grafu (prvky množiny) nakreslíme do radu a všetky šípky hrán budú smerovať jedným smerom. Vlastnosti čiastočne usporiadanej množiny zaručujú, že graf neobsahuje žiadne cykly.

Postup pri hľadaní jedného z možných riešení je veľmi jednoduchý a môžeme ho vyjadriť takto: Začneme výberom prvkov, ktorým nepredchádza žiadny iný prvok (musí byť aspoň jeden, pretože inak by graf obsahoval cyklus). Tieto prvky odstránime z množiny S a zaradíme ich do vytváraného zoznamu, reprezentujúceho lineárne usporiadanie. Zostávajúca množina je stále čiastočne usporiadaná, a preto môžeme na ňu aplikovať ten istý algoritmus až dovtedy, kým nebude prázdna.

Aby sme tento algoritmus mohli presnejšie zapísať, musíme sa rozhodnúť o spôsobe reprezentácie množiny a čiastočného usporiadania.

Každý prvok môžeme charakterizovať pomocou troch atribútov:

Prvky spojíme do lineárneho zoznamu prepojeného smerníkom dalsi. Budeme predpokladať, že kľúče sú vyjadrené celočíselnými hodnotami (ale nie nutne po sebe idúcimi číslami 1 až n).

Zoznam nasledovníkov bude tiež lineárny zoznam pripojený k danému prvku pomocou smerníka priv. Prvok v tomto zozname bude teda ukazovať na nasledovníka pomocou smerníka vrch a na ďalší prvok tohto zoznamu pomocou smerníka dalsi.

 

 

Teda odpovedajúce typy údajov budú mať nasledujúci tvar:

Type psmer = ­ vrchol;

lsmer = ­ prives;

vrchol = record

kluc:hodnota;

pocit:integer;

dalsi:psmer;

priv:lsmer;

end;

prives = record

vrch:psmer;

dalsi:lsmer;

end;

 

 

Celý program je možné zapísať po častiach nasledujúcim spôsobom:

 

 

Celý program je možné zapísať nasledujúcim spôsobom:- deklarácie

program TOPSORT;

Type hodnota=integer;

psmer = ­ vrchol;

lsmer = ­ prives;

vrchol = record

kluc: hodnota;

pocit:integer;

dalsi: psmer;

priv: lsmer;

end;

prives = record

vrch: psmer;

dalsi: lsmer;

end;

var

hlava, chvost: psmer;

z:integer;

 

 

Hodnotou funkcie je smerník k vrcholu, v ktorom je uložené w

 

function Ukazuj(w: hodnota): psmer;

var

h:psmer;

begin

h:=hlava; chvostV.kluc:=w; {Ulozenie zarazky}

while h­ .kluc<>w do h:=h­ .dalsi;

if h=chvost then

begin

{Ziadny prvok s hodnotou w nebol ulozeny}

new(chvost); z:=z+1;

chvost­ .pocit:=0; chvost­ .dalsi:= nil;

chvost­ .priv:=nil;

h^.dalsi:=chvost;

end;

Ukazuj:=h

end; {Koniec funkcie Ukazuj}

 

 

Vytvorenie štruktúry

procedure Struktura(var hlava, chvost: psmer);

var x, y: hodnota;

p, q: psmer;

t: lsmer;

str: string;

ff: text;

begin { Vytvorenie prazdnej struktury }

z:=0;

new(hlava); chvost:=hlava;

with hlava­ do

begin dalsi:=nil; priv:=nil; pocit:=0; end;

{Vstupna faza}

writeln('Meno suboru, v ktorom su udaje: '); Readln(str);

assign(ff,str); rewrite(ff);

read(x);

while x<>0 do

begin

readln(y); writeln(ff,'x= ',x:3,'=> y= ',y:3);

p:=Ukazuj(x); q:=Ukazuj(y);

new(t);

t­ .dalsi:=p­ .priv; t­ .vrch:=q;

p­ .priv:=t; inc(q­ .pocit);

read(x)

end;

close(ff);

writeln('Pocet vrcholov je ',z:3);

end;{struktura}

 

 

 Hľadanie členov s nulovým počtom predchodcov

 

procedure Nul_predchodcovia(var hlava: psmer);

{Hladanie clenov s nulovym poctom predchodcov}

var p, q: psmer;

begin

p:=hlava; hlava:=nil;

while p<>chvost do

begin

q:=p; p:=p­ .dalsi;

if q­ .pocit=0 then

begin

q­ .dalsi:=hlava; hlava:=q;

end;

end;

end; { Nul_predchodcovia }

 

 

 Výstup lineárneho usporiadania

Procedura CUMvLIN(hlava: psmer);

var t: lsmer;

p, q: psmer;

begin

{Vystupna faza}

q:=hlava;

while q<>nil do

begin

writeln(q­ .kluc); writeln(ff,q­ .kluc);

z:=z-1;

t:=q­ .priv; q:=q­ .dalsi;

while t<>nil do

begin

p:=t­ .vrch; dec(p­ .pocit);

if p­ .pocit = 0 then

begin

{Ulozenie do zoznamu prvkov s nulovym poctom predchodcov}

p­ .dalsi:=q; q:=p;

end;

t:=t­ .dalsi;

end;

end;

if z<>0 then

begin

writeln(' Tato mnozina nie je ciastocne usporiadana');

writeln(ff,'Tato mnozina nie je ciastocne usporiadana')

end;

end; { CUMvLIN }

 

 

 

 Hlavný program

begin {hlavny program}

Struktura(hlava, chvost);

Nul_predchodcovia(hlava);

CUMvLIN(hlava);

readln;

end.