====== 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