======= Quicksort ====== http://gitlab.fjfi.cvut.cz/culikzde/zalg/-/blob/master/quicksort/quicksort.cpp ===== Funkce quicksort (int r, int s) ===== Funkce **quicksort** sparametry **(r, s)** má setřídit část globálního pole **a**. \\ Třídí prvky **a[r]**, ..., **a[s]**. \\ ==== Výběr pivotu ==== Vybereme si jednu hodnotu z této části, nazveme ji //pivot// a uložíme do proměnné **h**. h = a [ (r+s)/2 ]; :?: Tuto značku budu používat pro poznámky, které se napoprvé nezdají důležité, ale jsou významné pro správně fungující program. \\ :?: **int / int** ... celočíselné dělení, pro nezáporné parametry zaokrouhluje dolu \\ :?: výraz **(r+s)/2** je větší nebo roven **r** a menší nebo roven **s** \\ :?: hodnota **h** se skutečně nalézá někde v posloupnosti **a[r]**, ..., **a[s]** r s .----------------------------------------------. | | h | | .----------------------------------------------. ==== Rozdělení na tři části ==== Posloupnost **a[r]**, ..., **a[s]** chceme rozdělit na tři části (krajní mohou být prázdné). .---------------------------------------------------. | a[t] <= h | a[t] = h | a[t] >= h | .---------------------------------------------------. :?: Hodnoty **h** mohou být umístěny do libovolné části. \\ ==== Postupujeme zdola a shora ==== Zavedeme dvě proměnné **i** a **k**. \\ Do proměnné **i** uložíme index levého okraje **r** a budeme ji postupně **zvětšovat**. \\ Do proměnné **k** uložíme index pravého okraje **s** a budeme ji postupně **zmenšovat**. \\ int i = r; int k = s; while (i <= k) { /* ... */ } r s .----------------------------------------------. | | h | | .----------------------------------------------. i ---> <--- k Pokud uvnitř **while** cyklu potkáme hodnoty **ostře menší** než **h**, proměnnou **i** zvětšíme. \\ Pokud potkáme hodnoty **ostře větší** než **h**, proměnnou **k** zmenšíme. :?: V definici krajních oblastí máme **"< ="** a **"> ="**, ale zde používáme **"<"** a **">"**. \\ Máme jistou, že hodnota **h** je někde uprostřed a funguje jako zarážka. while(i <= k) { while (a[i] < h) i++; while (a[k] > h) k--; /* ... */ } **i** a **k** tedy přeskočí ostře menší a ostře větší hodnoty. \\ hodnoty rovné **h** nepřeskakujeme, ponecháme si je jako zarážku. r s .------------------------------------------------------------------. | a[ ] <= h | zde se někde nalézá h | a[ ] >= h | .------------------------------------------------------------------. i ---> <--- k :?: Prohozenou hodnotu //"házíme před sebe ve směru pohybujícího se indexu"//. \\ Prohozená hodnota bude //"zarážkou"// pro příští průchody cyklem. ==== Prohození hodnot ==== Po provedení menších **while** cyklů je situace následující: r i k s .---------------------------------------------------------------------------------------. | a[ ] <= h | a[i] >= h | | a[k] <= h | a[ ] >= h | .---------------------------------------------------------------------------------------. Po skončení obou menších **while** cyklů musí platit obrácené podmínky cyklů ** a[i] > = h ** , ** a[k] < = h **. Pokud stále platí **i < = k**, hodnoty **a[i]** a **a[k]** prohodíme. \\ Indexy **i** a **k** zvětšíme a zmenšíme. \\ :?: Zde se nám hodí definice krajních oblastí s neostrou nerovností if (i <= k) { swap (a[i], a[k]); i++; k--; } Situace po prohození: r i k s .---------------------------------------------------------------------------------------. | a[ ] <= h | | a[ ] >= h | .---------------------------------------------------------------------------------------. ==== Cyklus opakujeme, dokud se indexy nepřekříží ==== Pokud nyní **i < = k**, opakujeme vnější **while** cyklus. while (i <= k) { while (a[i] < h) i++; while (a[k] > h) k--; if (i <= k) { swap (a[i], a[k]); i ++; k --; } } :?: Pokud i = k, nemusím nic prohazovat, ale zvětšit a zmenšit indexy je **nezbytné** (jinak se program dostane do nekonečného cyklu) ==== Až cyklus skončí, rekuzivně zpracujeme okraje ==== Po skončení **while** cyklu \\ index **i** //"přeběhl"// do pravé oblasti, \\ index **k** //"přeběhl"// do levé oblasti. Levá oblast je tvořena prvky **a[r]**, ..., **a[k]**. \\ Pravá oblast je tvořena prvky **a[i]**, ..., **a[s]**. \\ r k i s .---------------------------------------------------------------------------. | a[ ] <= h | a[ ] = h | a[ ] >= h | .---------------------------------------------------------------------------. Prostřední oblast obsahuje samé hodnoty **h**, už ji nemusíme třídit. \\ Levou část (pokud není **prázdná** nebo **jednoprvková**) setřídíme rekurzivním voláním funkce **quicksort (r, k)**. \\ Obdobně oravou část setřídíme rekurzivním voláním funkce **quicksort (i, s)**. \\ if (r < k) quicksort (r, k); if (i < s) quicksort (i, s); :?: Kontroly **r < k** a **i < s** zabrání nekonečné rekurzi ==== Celá funkce quicksort ==== void quicksort (int r, int s) { int h = a [(r + s) / 2]; // r <= (r+s)/2 <= s int i = r; int k = s; while (i <= k) { while (a[i] < h) i++; while (a[k] > h) k--; if (i <= k) { swap(a[i], a[k]); i++; k--; } } if (r < k) quicksort (r, k); if (i < s) quicksort (i, s); } ===== Celý program ===== Vygenerujeme pseudonáhodné hodnoty, spustíme **funkci quicksort (0, N-1)**, vytiskneme a zkontrolujeme. #include #include /* srand, rand */ #include /* time */ using namespace std; const int N = 1000; // const int N = 1000000; int a [N]; inline void swap (int & a, int & b) { int t = a; a = b; b = t; } void quicksort (int r, int s) { int h = a [(r + s) / 2]; // r <= (r+s)/2 <= s int i = r; int k = s; while (i <= k) { while (a[i] < h) i++; while (a[k] > h) k--; if (i <= k) { swap (a[i], a[k]); i++; k--; } } if (r < k) quicksort (r, k); if (i < s) quicksort (i, s); } int main() { srand (time (NULL)); for (int i = 0; i < N; i++) a[i] = rand() % 50000; quicksort (0, N-1); bool ok = true; for (int i = 0; i < N; i++) { cout << a[i]; if (i < N - 1 && a[i] > a[i + 1]) { ok = false; cout << " CHYBA"; } cout << endl; } if (ok) cout << "O.K." << endl; } ===== Složitost ===== :!: Pocet instrukcí while cyklu ve funkci quicksort je úměrný počtu prvků posloupnosti **a[r]**, ..., **a[s]**. \\ V každém volání funkce quicksort ubyde alespoň jeden prvek (s hodnotou **h**) a zbylá dvě volání quicksort mají k zpracování o jeden prvek méně. \\ Tj. n + (n-1) + (n-2) + ... + 1 instrukcí, kde n je počet tříděných prvků v celém poli. \\ V nejhorším případě je složitost ** O (n^2) :!: Průměrná složitost je O (n log n), viz skripta konec kapitoly 5.1.7