====== Heap sort ======
Třídění haldou, skripta 5.1.6
Heap sort má složitost O(n log n), kde n je počet tříděných prvků
===== Formální rozložení prvků ve stromu =====
Je zadáno pole N hodnot a[0], ... a[N-1]. \\
Hodnoty boudou stále uloženy v poli, ale představíme si je ve stromu. \\
Do kořene stromu umístíme a[0]. \\
Do patra pod kořenem umístíme a[1] , a[2]. \\
O patro níže a[3], a[4], a[5], a[6]. \\
Následující patra budou mít 8, 16, 32, 64, ... prvků. \\
Dolní patro nemusí být kompletní.
.------------------------------------------.
| a[0] | a[1] | a[2] | a[3] | a[4] | a[5] |
.------------------------------------------.
a[0]
|
.-------------------------.
| |
a[1] a[2]
| |
.----------. .---------.
| | |
a[3] a[4] a[5]
Pod prvkem **a[i]** jsou prvky **a[2i+1]** , **a[2i+2]**. \\
( Pole **a** indexujeme od **0**. ) \\
( Pokud v jinem jazyce indexujeme od **1**, tak pod prvkem a[i] jsou prvky a[2i] , a[2i+1]. )
a[i]
|
.-----------------.
| |
a[2i+1] a[2i+2]
===== Halda =====
V poli si vybereme posloupnost prvku s indexy **i**, **i+1**, ..., **k**.
Řekneme, že prvky **a[i]**, **a[i+1]**, ..., **a[k]** tvoří **haldu**, \\
pokud pro všechny hrany stromu spojující prvky a[i], a[i+1], ..., a[k] \\
je prvek na horním konci hrany **větší nebo roven** než prvek na dolním konci hrany.
Tj. vybereme si několik za sebou jdoucích prvků v poli ( tato posloupnost může být i jedno-prvková nebo prázdná ). \\
Uvažujeme jen hrany spojující prvky v této posloupnosti (hrany které začínají nebo končí mimo tuto posloupnost nás nezajímají ).
Vezmeme prvek **a[t]**, index t je z naší posloupnosti **i < = t < = k**. \\
Pod prvkem **a[t]** mohou být prvky **a[2t+1]** , **a[2t+2]**. \\
Pokud **2t+1 < = k**, levý pod-prvek je součástí naší posloupnosti a požadujeme **a[t] > = a[2t+1]**. \\
Pokud **2t+2 < = k**, tak i pravý pod-prvek je z naší posloupnosti a požadujeme **a[t] > = a[2t+2]**. \\
===== Vytvoření haldy =====
Funkce **heapify** pracuje s posloupností prvků a[i], ... a[k]. \\
Na vstupu do funkce, předpokládáme, že a[**i+1**], ... a[k] již tvoří haldu. \\
Cheme celou posloupnost a[i], ... a[k] proměnit v haldu.
Vezmeme si nejvýše položený prvek **a[i]**, ten zatím nemusí splňovat požadavek na haldu. \\
Hodnotu a[i] porovnáme s hodnotami podřízených prvků (pokud podřízené prvky v rámci a[i], ... a[k] existují) \\
Pokud je a[i] větší nebo roven než podřízené prvky, je vše v pořádku. Následující prvky již haldu tvoří, můžeme funkci ukončit. \\
Jinak vybereme větší z podřízených prvků, jeho hodnotu prohodíme s hodnotou nadřízeného prvku a pokračujeme stejným postupem na místě podřízeného prvku.
Obrázek http://cdn.programiz.com/sites/tutorial2program/files/heapify-base-case.jpg
Hodnota a[i] původně na vrcholu vybrané části stromu padá dolů, \\
až pod ní budou prvky, které ji nepřevyšují, \\
nebo až je na nejnižším patře vybrané části stromu.
Obrázek http://cdn.programiz.com/sites/tutorial2program/files/heapfy-root-element-when-subtrees-are-max-heaps.jpg
==== Cyklus od vrcholu posloupnosti dolů ====
Začneme s prvkem a[i], pokud půjdeme níže, budeme měnit proměnnou **i**.
Cyklus, který kontroluje, zda je pod a[i] alespoň levý prvek.
while (2*i+1 <= k)
Poznamenáme si index levého prvku do nové proměnné **v**.
int v = 2*i+1;
==== Výběr většího podřízeného prvku ====
Zkusíme zda existuje také pravý prvek. \\
Pokud existuje porovnáme s levým prvkem. \\
Do proměnné **v** uložíme index většího pod-prvku. ( a víme, že **v** je z uzavřeného intervalu **i**, ... **k** )
if (v+1 <= k)
if (a[v+1] > a[v])
v = v + 1;
==== Prohození prvků ====
Nyní porovnáme **a[i]** ("horní prvek") s prvkem **a[v]** ("vítězný podřízený prvek"). \\
Pokud je a[i] menší, prohodíme hodnoty ulořené v a[i] a a[v] a pokračujeme na místě podřízeného prvku (kde je nyní hodnota padající ze shora ).
if (a[i] < a[v])
{
swap (a[i], a[v]);
i = v;
}
==== Funkce swap ====
Funkce **swap** dostane jako parametry **x** a **y** odkazy (**&**) na dvě celá čísla, která má prohodit. \\
V našem případě dostane odkazy na dva prvky pole **a[i]** , **a[v]** \\
Klíčové slovo inline doporučuje překladači nevolat podprogram, ale potřebné instrukce vložit v místě volání funkce.
inline void swap (int & x, int & y)
{
int t = x;
x = y;
y = t;
}
Parametry předávané odkazem jsem již potkali [[zalg:strom_add#vkladani_prvku_do_stromu|vkládaní prvku do stromu]]
==== Altertativní funce swap pracující s globálním polem a parametrem jsou indexy v poli ====
inline void alt_swap (int i, int k)
{
int t = a[i];
a[i] = a[k];
a[k] = t;
}
Na místo původního volání funkce **swap (a[i], a[v])** použijeme
alt_swap (i, v);
==== Ukončení cyklu ====
Nastavíme proměnnou **i** na příliš velkou hodnotu, která zastaví **while** cyklus.
else
{
i = k+1;
}
==== Celá funkce heapify ====
void heapify (int i, int k)
{
while (2*i+1 <= k)
{
int v = 2*i+1;
if (v+1 <= k)
if (a[v+1] > a[v])
v = v + 1;
if (a[i] < a[v])
{
swap (a[i], a[v]);
i = v;
}
else
{
i = k+1; // ukonceni cyklu
}
}
}
:!: Počet prováděných instrukcí je úměrný výšce stromu s vybranými prvky a[i], ... a[k].
Pro ilustraci: \\
http://www.youtube.com/watch?v=HqPJF2L5h9U \\
http://www.youtube.com/watch?v=B7hVxCmfPtM (indexují od 1 ) \\
===== Třídění =====
Nejprve o uspořádání prvků v poli nic nevíme.
==== Vytváříme haldu - od konce pole k prvnímu prvku ====
Jednoprvková posloupnost s indexy **n-1, ... n-1** je z definice haldou. \\
Přidáme jeden prvek a použijeme funkci **heapify** na dvojprvkovou posloupnost s indexy **n-2, ... n-1**. \\
Budeme přidávat prvky na začátek, funkci **heapify** upravovat hodnoty, aby tvořily haldu.
for (int i = n-1; i >= 0; i--)
heapify (i, n-1);
Použijeme heapify (i, n-1) pro i = n-1, n-2, ..., 2, 1, 0. \\
Pro mnoho hodnot bude posloupnost umístěna v dolním patře. \\
Nebo bude v dolních dvou patrech, ale nebude obsahovat hranu. \\
Můžete se podívat na **while (2*i+1 <= k)**, kde **k=n-1** a pozměnit počáteční hodnotu **for** cyklu.
==== Odebereme první největší prvek, uložíme na konec pole ====
Celá posloupnost a[0], ... a[n-1] tvoří haldu. \\
Tedy a[0] je největší prvek, uložíme ho na konec pole. \\
Prvek, který byl na konci pole zkusíme umístit do a[0]. \\
Nově umístěný prvek necháme spadnout na správné místo. \\
Haldu od konce postupně zkracujeme, jsou tam umístěny již setříděné hodnory, které nechceme znovu zamíchat do třídění.
for (int i = n-1; i > 0; i--)
{
swap (a[0], a[i]); // odebrat hodnotu z vrcholu a umistit na soucasny konec
heapify (0, i-1); // zbyle hodnoty opravit, aby opet tvorily haldu
}
Obrázek http://stackoverflow.com/questions/41212072/ascending-and-descending-heapsort \\
Zeleně jsou zobrazeny uložené/setříděné hodnoty. \\
Červeně prohazované/nově nasazené hodnoty. \\
Žlutě je nakreslena zmenšující se halda.
==== Funkce heapsort ====
void heapsort (int n)
{
for (int i = n-1; i >= 0; i--)
heapify (i, n-1);
for (int i = n-1; i > 0; i--)
{
swap (a[0], a[i]);
heapify (0, i-1);
}
}
:!: Počet instrukcí funkce heapify je úměrný výšce stromu. \\
Výšku stromu shora odhadneme logaritmem o základu 2 z počtu prvků (zvětšeným o 1). \\
Oba **for** cykly proběhnou **n**-krát. \\
Celkem O (n log n)
===== Vstupní hodnoty, tisk a kontrola hodnot =====
srand (time (nullptr));
for (int i = 0; i < N; i++)
a[i] = rand() % 10000;
heapsort (N); // trideni
bool ok = true;
for (int i = 0; i < N; i++)
{
cout << a[i];
if (i < N-1 && a[i + 1] < a[i]) { ok = false; cout << " CHYBA"; }
cout << endl;
}
===== Celý program =====
#include
#include
using namespace std;
const int N = 1000000;
int a[N];
inline void swap (int & x, int & y)
{
int t = x;
x = y;
y = t;
}
void heapify(int i, int k)
// vytvorit hromadu z a[i] ... a[k]
// za predpokladu, ze a[i+1] ... a[k] jiz hromadu tvori
{
while (2 * i + 1 <= k)
{
int v = 2 * i + 1;
if (v + 1 <= k)
if (a[v+1] > a[v])
v = v + 1;
if (a[i] < a[v])
{
swap (a[i], a[v]);
i = v;
}
else
{
i = k+1; // ukonceni cyklu
}
}
}
void heapsort (int n)
// setridit a[0] ... a[n-1]
{
for (int i = n-1; i >= 0; i--)
heapify (i, n-1);
for (int i = n-1; i > 0; i--)
{
swap (a[0], a[i]);
heapify (0, i-1);
}
}
int main()
{
srand (time (nullptr));
for (int i = 0; i < N; i++)
a[i] = rand() % 10000;
heapsort (N);
bool ok = true;
for (int i = 0; i < N; i++)
{
cout << a[i];
if (i < N-1 && a[i + 1] < a[i]) { ok = false; cout << " CHYBA"; }
cout << endl;
}
if (ok)
cout << "O.K." << endl;
}
Mini literatura: http://cs.wikipedia.org/wiki/%C5%98azen%C3%AD_haldou