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 vyhodno
covať 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úv
ajú bližšie k ich definitívnym pozíciám. Tento proces vnajtriviá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 p
rincí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:
Z tretej vlastnosti vyplýva, že v k
oreni stromu sa nachádzanajväčš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;
Naučíme sa nový typ algoritmu - binárne vyhľa
dá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; |