Algoritmický problém triedenia

 

 

 

Problém triedenia je definovaný pre postupnosti prvkov, ktoré sú

z lineárne usporiadanej množiny, t.j. pre ľubovoľné dva rôzne prvky

z tejto množiny je daný vzťah medzi nimi.

 

 

 

Formulácia problému

Nech K je lineárne usporiadaná množina (reflexívna, tranzitívna a antisymetrická množina). Nech n je prirodzené číslo, nł 1,

a nech A = (a1, a2,..., an), ai Î K pre 1Ł iŁ n, je postupnosť prvkov množiny K.

Algoritmický problém triedenia spočíva v nájdení algoritmu, ktorý

ap(1) Ł ap(2) Ł ... Ł ap(n),

kde p(1), p(2),... p(n) je permutácia prvkov 1, 2, ... , n.

 

 

Algoritmy triedenia sú vyhodnocované z hľadiska počtu porovnaní a počtu presunov prvkov, ktoré patria do uvažovanej lineárne usporiadanej množiny.

Obidva počty sú u skoro všetkých algoritmov závislé od toho, akými prvkami je tvorená a v akom poradí sú na vstupe.

Budeme vyhodnocovať všetky základné algoritmy v najlepšom, v najhoršom a priemernom prípade.

 

Označenie

PAnajlep(n) - počet porovnaní, ktoré algoritmus A vykoná pre postupnosť n prvkov v najlepšom prípade,

PAnajhor(n) - počet porovnaní, ktoré algoritmus A vykoná pre postupnosť n prvkov v najhoršom prípade,

PApriem(n) - počet porovnaní, ktoré algoritmus A vykoná pre postupnosť n prvkov v priemernom (očakávanom) prípade,

SAnajlep(n) - počet presunov, ktoré algoritmus A vykoná pre postupnosť n prvkov v najlepšom prípade,

SAnajhor(n) - počet presunov, ktoré algoritmus A vykoná pre postupnosť n prvkov v najhoršom prípade,

SApriem(n) - počet presunov, ktoré algoritmus A vykoná pre postupnosť n prvkov v priemernom prípade.

 

 

Triedenie vsúvaním

 

procedure Vsuvanie(var a: prvky; n: integer);

{Procedúra utriedi postupnosť reálnych čísel na základe princípu

-- do už utriedenej postupnosti vsunúť prvok na miesto, kam patrí;

-- začať s jednoprvkovou postupnosťou, ktorá je vždy utriedená}

var i, j: integer; {Riadiace premenne cyklov}

x:real; {Hodnota priebezneho najvacsieho prvku}

begin

for i:= 2 to n do

{i-ty prvok je vsuvany do uz utriedenej postupnosti

i-1 prvkov}

begin

j:=i-1; x:= a[i];

while (j>=1) and (a[j]>x) do

{hladanie pozicie, kam ma byt i-ty prvok vsunuty}

begin

a[j+1]:=a[j];

j:=j-1

end;

a[j+1]:=x;

{zaradenie i-teho prvku na j-tu poziciu}

{po tomto kroku je uz utriedenych prvych i prvkov}

end;

end; {Vsuvanie}

 

Cyklus pre i je opakovaný (n-1)-krát,

cyklus pre j sa môže opakovať 0 (ak prvok a[i] patrí na koniec už utriedenej postupnosti) až (i-1)-krát (ak prvok a[i] patrí na začiatok), preto počet porovnaní prvkov postupnosti medzi sebou je v najlepšom prípade rovný n-1 a v najhoršom prípade n.(n-1)/2.

Počty priradení prvkov tejto postupnosti sú v najlepšom prípade 2.(n-1) a v najhoršom prípade 2.(n-1)+ (n-1).(n-2)/2. Priemerný prípad je pre analýzu v rámci rozsahu týchto skrípt zložitý, preto uvedieme len výsledok O(n^2) počet porovnaní a tiež O(n^2) počet presunov (výmen).

 

Triedenie výberom

 

procedure Vyber(var a: prvky; n: integer);

{Procedúra utriedi postupnosť reálnych čísel na základe princípu

-- vybrať najväčší prvok a vložiť ho na miesto, kam patrí;

zostane jednoprvková postupnosť, ktorá je už utriedená;}

var i, j: integer;

k: integer;

max:real;

begin

for i:= 0 to n-2 do

begin

max:=a[1]; k:=1;

for j:= 2 to n-i do

if a[j]>max then begin max:= a[j];

k:=j;

end;

a[k]:=a[n-i]; a[n-i]:=max;

end;

end; {Vyber}

 

 

 

Triedenie výmenou

 

procedure Vymena(var a: prvky; n: integer);

{Procedúra utriedi postupnosť reálnych čísel na základe princípu

-- porovnávané sú susedné prvky j-tý a (j+1)-vý systematicky od

prvého (j=1) až po posledný (j=n-1), pričom v prípade, že (j+1)-vý prvok je väčší ako j-tý, nastáva ich výmena. Tak sa väčšie prvky

presúvajú bližšie k ich definitívnym pozíciám. Tento proces v

najtriviálnejšom prípade opakujeme (n-1)-krát.

}

var i, j: integer;

pom:real;

q: boolean;

begin

i:=0;

q:= true;

while (i<=n-2) and q do

begin

q:=false;

for j:= 1 to n-i-1 do

if a[j]>a[j+1] then

begin

pom:=a[j]; a[j]:=a[j+1]; a[j+1]:=pom;

q:= true;

end;

i:=i+1

end;

end; {Vymena}

 

 

Metóda

Pnajlep(n)

Pnajhor(n)

Ppriem(n)

Snajlep(n)

Snajhor(n)

Spriem(n)

Vsúvanie - A

O(n)

O(n2)

O(n2)

O(n)

O(n2)

O(n2)

Výber-B

O(n2)

O(n2)

O(n2)

O(1)

O(n2)

O(n2)

Výmena-C

O(n2)

O(n2)

O(n2)

0

O(n2)

O(n2)

 

 

 

 

Quicksort

Tento algoritmus je typickým predstaviteľom algoritmov založených na rekurzii a na aplikácii metódy delenia problému na podproblémy toho istého typu ale menších rozmerov (metóda sa tiež nazýva rozdeľuj a panuj).

Celý algoritmus je založený na jednoduchom princípe:

Zvolíme prvok X (neskôr sa vrátime k tomu, ako ho vhodne zvoliť) a utrieďované prvky poprehadzujeme v poli tak, aby v ľavej časti boli len prvky menšie alebo

rovné ako prvok X a v pravej časti len prvky väčšie alebo rovné ako prvok X. Po tomto rozdelení je evidentné, že prvky, ktoré ležia v ľavej časti poľa, zostanú v tejto časti aj po úplnom utriedení a to isté platí o pravej časti poľa. Na poradí prvkov s rovnakou hodnotou nezáleží, takže aj prípadný výskyt viacerých prvkov s rovnakou hodnotou X nespôsobí problémy. Teraz postačuje utriediť samostatne ľavú a samostatne pravú časť poľa. To sú vlastne dva menšie podproblémy rovnakého typu, ale menšie, čo sa týka počtu prvkov. Na tie aplikujeme podobnú metódu. Ak úsek obsahuje jeden prvok, tak tento úsek je už utriedený a už nie je treba nič robiť.

Popíšeme, ako je možné vhodne poprehadzovať prvky podľa našej potreby. Na to potrebujeme dva pomocné indexy ukazujúce do práve spracovávaného úseku poľa. Index ii začína ľavom okraji úseku a zväčšuje sa, pokiaľ nenájdeme prvok väčší ako X. Ten nepatrí do vytváranej ľavej časti a má sa presunúť niekam vpravo. Podobne s indexom jj postupujeme od pravého okraja úseku smerom doľava, pokiaľ nenájdeme prvý menší prvok ako X. Ten sa má presunúť do ľavej časti. Oba nájdené prvky spolu vymeníme. Potom pokračujeme rovnakým spôsobom v pohybe indexov ii a jj smerom do stredu a vymieňame dovtedy, kým sa oba indexy nestretnú. V tomto okamihu je celý úsek rozdelený na ľavú časť s menšími prvkami ako X a pravú časť s väčšími prvkami. Hranica oboch úsekov je daná polohou indexov ii a jj. časová zložitosť tohoto delenia je lineárna, počet operácií je úmerný počtu prvkov v úseku.

Koľko delení bude použitých bude, závisieť od výberu prvku X. Najvhodnejším výberom prvku X je práve stredný prvok, ktorý rozdelí pôvodnú postupnosť na dva skoro rovnaké úseky.

Najlepší prípad je delenie vždy na polovicu, t.j. log N delení. Dá sa ukázať, že tento algoritmus má priemernú časovú zložitosť O(N.log N).

Uvedieme celý program aj s výpismi a generovaním náhodných prvkov.

 

program Quicksort;

{Aplikovanie metody quicksort}

const nmax=100;

type pole =array [1..nmax]of integer;

var a: pole;

n: integer;

d,h: integer;

pocpor: integer; {pocet porovnani}

procedure vypis(a:pole; n:integer);

var i:integer;

begin

for i:=1 to n do write(a[i]:4);

writeln

end; {vypis}

procedure rozdel( i,j:integer; var d, h: integer);

{prvy interval po rozdeleni <i, d>, druhy: <h, j>}

{Pouzitie pri deleni useku prvkov}

var ind: integer;

x, pom: integer;

ii, jj: integer;

begin

writeln(' Skumany interval ', i:3, j:4);

if abs(j-i)>=2 then

begin

ind:=(i+j) div 2;

X:=a[ind];

ii:=i; jj:=j;

repeat

while (a[ii]<x) do inc(ii);

while (a[jj]>x) do dec(jj);

if ii<jj then

begin

pom:=a[ii]; a[ii]:=a[jj]; a[jj]:=pom;

jj:=jj-1; ii:=ii-1;

end else

if ii=jj then {indexy sa stretli}

begin inc(ii); dec(jj)

end

until ii>jj;

d:=jj; h:=ii;

end;{rozdel}

procedure utried(var a:pole; z,k:integer);

{Utriedi postupnost prvkov}

var d,h:integer;

pom: integer;

begin

if abs(z-k)<=1 then

begin inc(pocpor);

if a[z]>a[k] then

begin pom:= a[z]; a[z]:= a[k]; a[k]:= pom end

end else

begin

rozdel(z,k,d,h);

utried(a,z,d);

utried(a,h,k);

end

end;{utried}

procedure generuj(var a:pole; n:integer);

var i:integer;

begin

for i:=1 to n do a[i]:=random(20);

end;{gener}

begin {hlavny}

pocpor:=0;

randomize;

n:=15;

generuj(a,n);

writeln;

vypis(a,n);

readln;

utried(a,1,n);

vypis(a,n);

writeln('Celkovy pocet porovnani:',pocpor:4);

readln

end.

 

Triedenie haldovaním

Halda je binárny strom, v ktorom sú splnené nasledujúce podmienky:

  1. Na každej hladine od prvej až po predposlednú je maximálny možný počet vrcholov, t. j. na hladine k-tej je 2^{k-1} vrcholov.
  2. Na poslednej hladine sú všetky vrcholy umiestnené čo najviac vľavo, t. j. keď prechádzame zľava napravo, tak najprv všetky vrcholy predposlednej hladiny majú po dvoch synoch, potom môže existovať jeden vrchol s jedným synom. Ďalšie vrcholy nemajú žiadnych synov.
  3. Pre každý vrchol platí, že hodnota v ňom uložená nie je menšia ako hodnota jeho synov.

Z tretej vlastnosti vyplýva, že v koreni stromu sa nachádza

najväčšia hodnota. Koreň je dostupný priamo, takže nájsť maximum je veľmi jednoduché.

Je teda celkom prirodzené použiť haldu pre triedenie prvkov. úloha bude spočívať vo vytvorení haldy a potom triedenie sa bude vykonávať tak, že odoberieme maximum a haldu prehaldujeme, čo znamená prechod haldou po jej jednej vetve a to je možné urobiť v čase log N, ak N je počet prvkov.

Vytvorenie haldy je možné urobiť postupným pridávaním prvkov na poslednú hladinu haldy a prehaldovaním. Jeden prvok sám tvorí haldu. Z uvedeného vyplýva, že časová zložitosť tohto triedenia je úmerná N.log N, ak N je počet triedených prvkov.

Uvedieme úplný program pre triedenie haldovaním.

 

 

program haldovanie;

{Testovanie procedury vyhalduj}

const nmax=100;

type pole=array[1..nmax] of integer;

var a:pole; n:integer;

j, jj:integer;

pom:integer;

procedure Vyhalduj(var a:pole; k,l:integer);

{Vyhalduje cast pola v useku k..l, k<l}

var i, t:integer;

max:integer;

begin

i:=k;

while 2*i<=l do

begin

t:=2*i; max:=a[t];

if (2*i+1<=l) then

if (a[2*i+1]>max) then

begin

t:=2*i+1;

max:=a[t];

end;

if max>a[i] then begin a[t]:=a[i];

a[i]:=max

end;

i:=t

end;

end;{Vyhalduj}

begin

randomize;

writeln('Zadaj pocet prvkov'); readln(n);

for j:=1 to n do

begin a[j]:=random(100); write(a[j]:3);

end;

writeln;

{Vytvorenie haldy}

for j:=n-1 downto 1 do Vyhalduj(a,j,n);

{Utriedenie haldy}

for jj:=1 to n-1 do

begin pom:=a[1]; a[1]:=a[n-jj+1]; a[n-jj+1]:=pom;

Vyhalduj(a, 1,n-jj);

end;

for j:=1 to n do write(a[j]:3);

writeln;

readln;

end.

 

Vyhľadávanie

 

štruktúra tabuľka:

const
  max=100;

type
  info = record
    meno:string;
    x,y:integer;
  end;

  tabulka = record
    pocet:0..max;
    p:array[1..max] of info;
  end;

function CitajRiadok(var t:Text):info;
var
  z:char;
begin
  with Result do begin
    meno:=''; read(t,z);
    while z<>';' do begin
        meno:=meno+z; read(t,z);
    end;
    readln(t,x,y);
  end;
end;

procedure zarad(var tab:tabulka; pp:info);
begin
  with tab do begin
    inc(pocet); p[pocet]:=pp;
  end;
end;

function hladaj(tab:tabulka;  m:string):integer;
begin
  Result:=tab.pocet;
  while (Result>0) and (tab.p[Result].meno<>m) do dec(Result);
end;

var
  tab: tabulka;

 

 

Binárne vyhľadávanie

 

Naučíme sa nový typ algoritmu - binárne vyhľadávanie - podobný, ako keď hľadáme v telefónnom zozname:

algoritmus binárneho vyhľadávania:

function hladaj(tab:tabulka;  m:string):integer;
var
  z,k,s:integer;
begin
  z:=1; k:=tab.pocet; s:=1;   {začiatok a koniec intervalu}
  while z<=k do begin
    s:=(z+k) div 2;           {stred intervalu}
    if tab.p[s].meno<m then z:=s+1
    else if tab.p[s].meno>m then k:=s-1
    else z:=k+1;
  end;
  if tab.p[s].meno=m then Hladaj:=s
  else Hladaj:=0;
end;

alebo:

function hladaj( tab:tabulka; m:string):integer;
var
  z,k,s:integer;
begin
  z:=1; k:=tab.pocet;
  while z<k do begin
    s:=(z+k) div 2;
    if tab.p[s].meno<m then z:=s+1
    else if tab.p[s].meno>m then k:=s-1
    else begin z:=s; k:=s end
  end;
  if (z=k) and (tab.p[z].meno=m) then Hladaj:=z
  else Hladaj:=0;
end;