Algoritmy třídění
Naučit se programovat neznamená jen naučit se programovací jazyk, ale také vytvářet dobré programové algoritmy. Co si budeme nalhávat, jde hlavně o matematické algoritmy. S řešením všemožných záludných problému se programátor (nebo spíš analytik) setkává neustále. Není proto divu, že se najde spousta problémů, které už někdo řešil a vyřešil. Jedním z takových problémů je třídění. Na to, jak lze setřídit velké množství dat, se podíváte právě teď. Řešení algoritmů obecně není nijak závislé na programovacím jazyku, proto se při popisování programovacích jazyků s algoritmizací moc nesetkáte. Přesto je to mnohdy velice podstatná část návrhu programu a jeho optimalizace.
Funkce qsort()
Nejjednodušším řešením jak setřídit data v programovacím jazyku C je použití knihovní funkce qsort().
Pro zopakování ukážu jednoduchý příklad jejího použití. Její použití je efektivnější než použití následně probíraných algoritmů. Ty zde ukazuji jen jako příklad, abyste se nebáli převádět algoritmy do C. A také, abyste viděli, jak vám může pomoci mít rozhled nejen v syntaxi programovacího jazyka.
Vzhledem k tomu, že zde budu ukazovat třídění jednoduchého
datového pole typu int, připomenu, jak by k třídění tohoto pole šlo
použít funkce qsort()
.
Prvním argumentem funkce qsort()
je „prázdný“ ukazatel na pole,
které se bude třídit. Druhým argumentem je
počet položek (rozměr) pole, třetím argumentem je velikost jedné
položky (v příkladě je to počet bajtů typu int) a posledním
argumentem je ukazatel na funkci (compar), podle které se budou
porovnávat dva prvky z pole.
Tato porovnávací funkce musí mít dva parametry – ukazatele,
kterými budou předány dvě hodnoty z pole na porovnání. Návratovou
hodnotou této porovnávací funkce je typ int, podle kterého funkce
qsort()
pozná, který z porovnávaných
prvků je větší. V případě, že větší je první prvek, musí vrátit
porovnávací funkce kladnou hodnotu (1), pokud je menší vrátí
hodnotu zápornou (-1) a pokud se rovnají,
vrátí 0.
Tím „větší prvek“ myslím ten, který má být v setříděném
poli dále než ten „menší“. Pokud návratové hodnoty v
porovnávací funkci obrátíte, dojde k setřídění od největšího po
nejmenší, ale to jsem snad ani nemusel dodávat :).
Nebudu tu psát celý příklad s výstupem, jen ukážu tu podstatnou část.
Buble sort
Algoritmus bublinkového třídění je poměrně jednoduchý ale ne moc efektivní. Procházíte několikrát celé pole (odzadu do předu) a porovnáváte sousední prvky. Pokud nejsou prvky správně seřazeny za sebou, prohodíte je. Takto nejmenší prvky „probublávají“ směrem k začátku (nebo to může být obráceně, chcete-li :-). Začínáte od konce pole a „probubláváte“ směrem na začátek. Po prvním průchodu máte zajištěno, že nejmenší prvek probublal na začátek pole a tak s ním v dalším cyklu už nemusíte počítat. Při pochopení tohoto algoritmu se soustřeďte nejdříve na pochopení jednoho kroku „probublávaní“, tj. jak vnitřní cyklus algoritmu prochází pole odzadu směrem dopředu a porovnává dva sousedící prvky pole.
- /*------------------------------------------------*/
- /* c41/bubble.c */
- #include <stdio.h>
- #include <stdlib.h>
- #include <time.h>
- #define ROZMER 10
- {
- /* schvalne, co by se stalo, kdyby byl ROZMER lichy ? */
- pole[i + ROZMER / 2]);
- /* spravna odpoved zni .... chvilka napeti ...
- * posledni prvek z pole by se nevypsal. */
- }
- {
- /* nejdrive inicializuji pole nahodnymi hodnotami a vypisi jej */
- vypis_pole(-1, pole);
- /* ted pole setridim algoritmem bubble sort */
- /* mensi cislo probubla o jeden prvek smerem k zacatku */
- pom = pole[bublani - 1];
- pole[bublani - 1] = pole[bublani];
- pole[bublani] = pom;
- }
- }
- /* vypise se mezivysledek, kde "cykl" nejmensich cisel
- * uz je zarucene na zacatku pole */
- vypis_pole(cykl, pole);
- }
- }
- /*------------------------------------------------*/
Možný výstup z programu:
Inicializace: Prvky pole (-1): /* zde je inicializované a 1: 26 6: 64 zatím nesetříděné, náhodně 2: 87 7: 53 vygenerované pole */ 3: 81 8: 6 4: 9 9: 23 5: 17 10: 7 ......... Prvky pole ( 1): /* po prvním "buble" cyklu nám 1: 6 6: 17 probublalo na začátek 2: 26 7: 64 nejmenší číslo 6. 3: 87 8: 53 Všimněte si, že se nejdříve 4: 81 9: 7 snažila probublat sedmička. Ostatní 5: 9 10: 23 čísla se jen posunuly o prvek níže. */ ........ Prvky pole ( 2): 1: 6 6: 9 2: 7 7: 17 3: 26 8: 64 4: 87 9: 53 5: 81 10: 23 ....... Prvky pole ( 3): 1: 6 6: 81 2: 7 7: 17 3: 9 8: 23 4: 26 9: 64 5: 87 10: 53 ...... Prvky pole ( 4): 1: 6 6: 87 2: 7 7: 81 3: 9 8: 23 4: 17 9: 53 5: 26 10: 64 ..... Prvky pole ( 5): 1: 6 6: 26 2: 7 7: 87 3: 9 8: 81 4: 17 9: 53 5: 23 10: 64 .... Prvky pole ( 6): 1: 6 6: 26 2: 7 7: 53 3: 9 8: 87 4: 17 9: 81 5: 23 10: 64 ... Prvky pole ( 7): 1: 6 6: 26 2: 7 7: 53 3: 9 8: 64 4: 17 9: 87 5: 23 10: 81 .. Prvky pole ( 8): /* Pole je seřazeno, ale buble-sort 1: 6 6: 26 o tom neví. Proto pokračuje ve třídění. */ 2: 7 7: 53 3: 9 8: 64 4: 17 9: 81 5: 23 10: 87 . Prvky pole ( 9): 1: 6 6: 26 2: 7 7: 53 3: 9 8: 64 4: 17 9: 81 5: 23 10: 87
Tučně zvýrazněné jsou místa v poli, kde je zaručeno, že je pole seřazeno. (Proto se vnitřní průchozí cyklus 'for' zkracuje.)
Algoritmus bubble sort má pevně daný počet operací, který závisí jen na rozměru pole. I kdyby bylo pole už seřazeno, bubble-sort by provedl stejný počet operací. Tomu lze trochu pomoci tak, že se bude kontrolovat, zda se při „bubble“ cyklu nějaké prvky zaměnili. Pokud ne, algoritmus se ukončí. Tuto optimalizaci jistě zvládnete naprogramovat sami (máte to za domácí úkol ;-).
Quick sort
Algoritmus quick sort (nebo též „řazení dělením“) je jeden z nejefektivnějších. Princip je asi následující. Řazené pole se rozdělí na dvě části tak, aby v levé (první, horní říkejte tomu jak chcete …) části pole byly všechny čísla menší, než čísla v pravé části. Aby se to dobře programovalo, vezme se jeden prvek z pole (třeba prostřední, to je fuk), říkejme mu pivot a hledá se a třídí pole tak, že čísla z levé části jsou menší a čísla z pravé části jsou větší než pivot. Na oba úseky se použije rekurzivně stejný algoritmus, a tak se pole rozděluje na menší a menší úseky, až je rozdělené na úseky délky 1, čímž je pole setříděno.
Příklad:
- /*------------------------------------------------*/
- /* c41/quick.c */
- #include <stdio.h>
- #include <stdlib.h>
- #include <time.h>
- #define ROZMER 10
- /**
- * funkce quicks se bude volat rekurzivne,
- * vzdy na levou a pravou cast
- */
- {
- size_t i;
- }
- }
- }
- {
- levy = levy_usek;
- pravy = pravy_usek;
- pivot = pole[(levy + pravy) / 2]; /* vyber pivota zhruba u prostred */
- vypis_usek(pole, "Pred:", levy_usek, pravy_usek);
- do {
- levy++;
- pravy--;
- pom = pole[levy];
- pole[levy] = pole[pravy];
- pole[pravy] = pom;
- levy++;
- pravy--;
- }
- vypis_usek(pole, "Po:", levy_usek, pravy_usek);
- /* ted vime, ze vsechny cisla od "levy_usek" k "pravy"
- jsou mensi nebo rovna cislum od "levy" po pravy_usek */
- quicks(pole, levy_usek, pravy); /* razeni leve casti */
- quicks(pole, levy, pravy_usek); /* razeni prave casti */
- }
- {
- size_t cykl;
- }
- /* ted se pole setridi algoritmem quick sort */
- quicks(pole, 0, ROZMER - 1);
- }
- }
- /*------------------------------------------------*/
Pochopení tohoto algoritmu možná není nejsnazší. Ale věřte, že existují ještě „horší“ algoritmy na třídění, se kterými se můžete setkat ve zdrojových kódech. Ale teď už by vás snad něco takového nemělo rozházet. V knihkupectví určitě najdete nějaké knihy, které se zabývají právě algoritmizací úloh pro programování. Určitě není špatný nápad po nějaké šáhnout. Můžete si taky nastudovat skvělý web algoritmy.net.
A na závěr výstup z programu:
Start: 74 43 94 6 89 76 88 33 52 59 pivot: pole[4] = 89 Pred: 74 43 94 6 89 76 88 33 52 59 Po: 74 43 59 6 52 76 88 33 89 94 pivot: pole[3] = 6 Pred: 74 43 59 6 52 76 88 33 -- -- Po: 6 43 59 74 52 76 88 33 -- -- pivot: pole[4] = 52 Pred: -- 43 59 74 52 76 88 33 -- -- Po: -- 43 33 52 74 76 88 59 -- -- pivot: pole[2] = 33 Pred: -- 43 33 52 -- -- -- -- -- -- Po: -- 33 43 52 -- -- -- -- -- -- pivot: pole[2] = 43 Pred: -- -- 43 52 -- -- -- -- -- -- Po: -- -- 43 52 -- -- -- -- -- -- pivot: pole[5] = 76 Pred: -- -- -- -- 74 76 88 59 -- -- Po: -- -- -- -- 74 59 88 76 -- -- pivot: pole[4] = 74 Pred: -- -- -- -- 74 59 -- -- -- -- Po: -- -- -- -- 59 74 -- -- -- -- pivot: pole[6] = 88 Pred: -- -- -- -- -- -- 88 76 -- -- Po: -- -- -- -- -- -- 76 88 -- -- pivot: pole[8] = 89 Pred: -- -- -- -- -- -- -- -- 89 94 Po: -- -- -- -- -- -- -- -- 89 94 Konec: 6 33 43 52 59 74 76 88 89 94
A to je ode mne vše, přátelé!