mergeSort :: [Int] -> [Int]
mergeSort [] = []
mergeSort [x] = [x]
mergeSort s = merge (mergeSort u) (mergeSort v)
where (u,v) = splitAt (n ‘div‘ 2) s
n = length s
merge s [] = s
merge [] t = t
merge (x:u) (y:v) = if x≤y then x: merge u (y:v)
else y: merge (x:u) v
quickSort :: [Int] -> [Int]
quickSort [] = []
quickSort (p:t) = quickSort lt ++ [p] ++ quickSort ge
where lt = [ x | x <- t, x < p ]
ge = [ x | x <- t, x ≥ p ]
procedure BubbleSort(var pole:TPole; N:word);
var i,j, pom: integer;
begin
for j:=1 to N-1 do {probublavani provadime pro n-1 prvku}
for i:=1 to N-1 do {postupne pro vsechny prvky pred poslednim}
if pole[i]>pole[i+1] then {pokud je prvek mensi nez nasledujici}
begin {prehod prvky => probublavani vetsiho prvku polem}
pom:=pole[i+1]; {prvky snadno prohodime pomoci pomocne promenne pom}
pole[i+1]:=pole[i];
pole[i]:=pom
end
end;
Algoritmus postupně „bere“ jednotlivé prvky pole (//n// − 1 prvků) a posouvá je dále doprava, dokud jsou větší než prvek před nimi.
Pro začátek se podíváme podrobněji na určení složitosti algoritmu: Složitost algoritmu je evidentně O(//n//2), algoritmus prochází pole o //n// prvcích ve dvou vnořených cyklech, každý cyklus sestává přibližně z //n// operací (přibližně //n// operací pro každých //n// prvků, tj. celkem //n//2), počet výměn prvků a přiřazovacích operací ve vnitřním cyklu se projeví jako multiplikativní konstanta, kterou lze ve výsledku zanedbat (jedná se jen o konstantní operace, které jsou samy o sobě nezávislé na velikosti vstupních dat). Lepší složitosti se dá dosáhnout drobnými vylepšeními algoritmu. Pro velká vstupní data není složitost O(//n//2) nejvhodnější, nejrychlejší třídící algoritmus v nejhorším případě -- Heap Sort (třídění haldou) -- dosahuje složitosti lineárně logaritmické. Přesto v jednoduchém případě můžeme tento algoritmus využít.
=== Vylepšený Bubble sort ===
procedure BubbleSort(var pole:TPole; N:word);
var i,j, pom: integer;
konec: boolean;
begin
for j:=1 to N-1 do
begin {probublavani provadime pro n-1 prvku}
konec:=true; {pred zacatkem vnitrniho cyklu nastavime konec na true}
for i:=1 to N-1 do {postupne pro vsechny prvky pred poslednim}
if pole[i]>pole[i+1] then {pokud je prvek mensi nez nasledujici}
begin {prehod prvky => probublavani vetsiho prvku polem}
pom:=pole[i+1];
pole[i+1]:=pole[i];
pole[i]:=pom;
konec:=false {s prvkem se provedla vymena}
end;
if konec then Break {pokud nebyl ani jeden prvek v cyklu vymenen,
tj. vsechny prvky byly uz na svem miste,
ukoncime trideni (bylo by zbytecne setridene
prvky dale prochazet}
end
end;
Složitost algoritmu v nejhorším případě zůstává stejná (O(//n//2)), pokud je však vstupní pole už relativně setříděné, často si ušetříme zbytečné automatické procházení tím, že si zaznamenáme průchod, v kterém byly už všechny prvky umístěné ve správném pořadí -- konec třídění.
=== Třídění přímým výběrem minima ===
procedure PrimyVyber(var pole:TPole; N:word);
var i,j, min, pom: integer;
begin
for i:=1 to N-1 do {na tyto pozice budeme vybirat minimum}
begin
min:=i; {nastaveni pozice minima}
for j:=i+1 to N do {vyhledani mozneho minima v dalsich prvcich}
if pole[j] provedeni vymeny prvku}
pom:=pole[i];
pole[i]:=pole[min];
pole[min]:=pom
end
end
end;
Algoritmus postupně prochází pole a na dané místo hledá minimum ze zbývajících prvků. Ve for cyklu se prochází postupně (//n// − 1) + (//n// − 2) + (//n// − 3) + ... + 1 prvků, tj. po sečtení , řádově je tedy složitost opět O(//n//2).
=== Třídění přímým vkládáním ===
procedure PrimeVkladani(var pole:TPole; N:word);
{prvky postupne zarazujeme do leve casti pole - vytvarime setridene pole}
var i,j, pom: integer;
begin
for i:=2 to N do {prochazime nesetridenou cast pole}
begin
pom:=pole[i]; {ulozime si zatrizovany prvek}
for j:=i-1 downto 1 do {tento i-ty prvek zarazujeme do leve setridene casti pole}
if pole[j]>pole[j+1] then begin {jestlize je prvek vlevo vetsi, prvky prehodime}
pole[j+1]:=pole[j];
pole[j]:=pom
end
end
end;
Algoritmus prvky z pravé části pole přímo zatřiďuje do levé části, kde se utváří seřazená posloupnost, postupně procházíme 1 + 2 + 3 + 4 + 5 + ... + (//n// − 1) prvků jako v minulém případě, složitost je tedy také O(//n//2), jediný rozdíl tkví v tom, že tento způsob manipuluje při řazení přímo s jednotlivými prvky a ne jenom s jejich indexy.
=== Quick Sort (rekurzivní) ===
procedure QuickSort(var pole:TPole; Zac,Kon:integer);
{procedura setridi v poli usek od indexu Zac do indexu Kon}
var P: integer; {hodnota pro rozdeleni pole na useky - pivot}
pom: integer; {pomocna promenna pro vymenu prvku}
i,j: integer; {pracovni indexy pro vymezeni casti pole}
begin
i:=Zac; {na zacatku zabiraji mezni indexy cele pole}
j:=Kon;
P:=pole[(Zac+Kon) div 2]; {urcime si pivotni prvek - vezmeme prostredni prvek pole}
{idealni pivot by byl median - prostredni z hodnot v poli}
repeat {nalevo od pivota budeme radit mensi prvky, napravo vetsi prvky nez pivot}
while pole[i]P do dec(j); {posouvame pravy index, dokud neni na prvku mensim nez pivot}
if ij; {opakujeme, dokud nejsou obeti casti pole roztrideny podle pivota}
{Pole [Zac,Kon] je rozdeleno na 2 useky [Zac,j] a [i,Kon],
ktere zpracujeme rekurzivne (opet touto procedurou)}
if Zac
Funkce algoritmu je dostatečně zřejmá z komentářů, takže stručně o co jde: Quick Sort vychází z rekurzivního přístupu -- pokud máme třídit elementární pole o dvou prvcích, prvky jednoduše přehodíme na tu stranu, kam patří (porovnat je můžeme s pomyslnou hodnotou mezi nimi -- pivotním prvkem). Pokud máme třídit pole větší, roztřídíme takto podle zvoleného pivota i dva velké úseky pole, které potom dále necháme třídit rekurzivně stejnou procedurou, až budou nakonec uspořádány všechny části pole. Tento algoritmus je typickým příkladem použití metody „rozděl a panuj“ (Divide & Impera), kdy programátor daný problém rozdělí na více jednoduchých elementárních problémů, které lze snadno vyřešit (třeba rekurzí). Pokud si vzpomenete na tento princip a vybavíte si postup, určitě pak budete schopni jednotlivé detaily algoritmu snadno doprogramovat.
Průměrná složitost Quick Sortu je O(//n// log //n//), přesněji O(//n// log2//n//), táto složitost nastává, pokud je vždy ideálně vybrán pivot jako prvek s prostřední hodnotou, nejhorší případ O(//n//2) nastává, pokud je pivot vždy nešikovně vybrán jako hodnota maximální nebo minimální. Výše uvedená procedura vybírá pivotní prvek jako střed pole, záleží ale na rozložení vstupních dat, každý z prvků pole je pro určitou kombinaci ideální, výběr se proto někdy provádí náhodně (pomocí generátoru náhodných čísel), což zaručuje, že při různém výběru pivotů nebude volba pro vstupní data vyloženě nevhodná a složitost se tak více přiblíží k očekávané složitosti O(//n// log //n//). V průměrném případě se v pravé a levé části pole zaměňuje vždy polovina prvků -- pro jednotlivé zmenšující se úseky tedy , , , ... -- a vzhledem k tomu, že po záměně prvků se každé pole rozdvojuje na dvě poloviny (políčka si můžeme nakreslit do vyváženého binárního stromu), je složitost násobena dvojkovým logaritmem (zhruba zapisujeme jen logaritmus desítkový). Výsledná průměrná složitost je tedy intuitivně:
log //n// ( + + + + ...) = //n// log //n//
=====Kam dál=====
Grafická ukázka řazení použitím algoritmů -- [[http://www.cs.ubc.ca/~harrison/Java/sorting-demo.html]]
[[http://people.ksp.sk/~kuko/bak/index.html|applety mj animuje operace nad haldou]]
[[http://www.inf.fh-flensburg.de/lang/algorithmen/sortieren/heap/heapen.htm|Heap, Merge a Quick Sort -- pekne zpracovane]]
=====Literatura:=====
http://www.beranr.webzdarma.cz/algoritmy/trideni.html
http://www.sweb.cz/HonzaRug/Delphi/alg.htm
http://users.informatik.uni-halle.de
=====Vypracoval=====
Petr Kott
petr.kott@post.cz
Otázku si přečetl pan RNDr. Jan Bouda a rámcově prošel. Jeho podněty pro doplnění textu, opravy nesrovnalostí a odstranění matoucích či k otázce se nevztahujících textů byly do otázky zaneseny. Tato kontrola je jen **rámcová**, stále se může stát, že v otázce zůstala zapomenutá chybka či nesrovnalost, vyučující za toto nenese odpovědnost, berte tuto rámcovou kontrolu jako formu pomoci od vyučujících pro studenty.
~~DISCUSSION~~