Implementácia algoritmu Quicksort v jazyku Java
1. Prehľad
V tomto výučbe podrobne preskúmame algoritmus QuickSort so zameraním na jeho implementáciu v prostredí Java.
Budeme tiež diskutovať o jeho výhodách a nevýhodách a potom analyzujeme jeho časovú zložitosť.
2. Algoritmus QuickSort
Quicksort je algoritmus triedenia, ktorý využíva princíp rozdeľuj a panuj. Má priemer O (n log n) zložitosť a je to jeden z najpoužívanejších algoritmov triedenia, najmä pre veľké objemy dát.
Je dôležité mať na pamäti, že Quicksort nie je stabilný algoritmus. Stabilný algoritmus triedenia je algoritmus, v ktorom sa prvky s rovnakými hodnotami objavujú v rovnakom poradí v triedenom výstupe, ako sa vyskytujú vo vstupnom zozname.
Vstupný zoznam je rozdelený na dva podzoznamy prvkom nazývaným pivot; jeden podzoznam s prvkami menšími ako otočný čap a ďalší s podskupinami s prvkami väčšími ako otočný čap. Tento proces sa opakuje pre každý podzoznam.
Nakoniec sa všetky zoradené podzoznamy zlúčia a vytvoria konečný výstup.
2.1. Kroky algoritmu
- Vyberieme prvok zo zoznamu, ktorý sa nazýva pivot. Použijeme ho na rozdelenie zoznamu na dva podzoznamy.
- Usporiadame usporiadanie všetkých prvkov okolo otočného čapu - tie, ktoré majú menšiu hodnotu, sú umiestnené pred ním a všetky prvky väčšie ako otočný čap po ňom. Po tomto kroku je čap vo svojej konečnej polohe. Toto je dôležitý krok rozdelenia.
- Vyššie uvedené kroky rekurzívne použijeme na oba podzoznamy vľavo a vpravo od otočného čepu.
Ako vidíme, quicksort je prirodzene rekurzívny algoritmus, ako každý prístup rozdelenia a panovania.
Urobme si jednoduchý príklad, aby sme tomuto algoritmu lepšie porozumeli.
Arr [] = {5, 9, 4, 6, 5, 3}
- Predpokladajme, že pre jednoduchosť vyberieme 5 ako otočný čap
- Najskôr dáme všetky prvky na menej ako 5 na prvú pozíciu poľa: {3, 4, 5, 6, 5, 9}
- Potom to zopakujeme pre ľavé čiastkové pole {3,4}, pričom ako otočný čap vezmeme 3
- Neexistujú žiadne prvky menšie ako 3
- Aplikujeme quicksort na čiastkové pole vpravo od otočného bodu, t. J. {4}
- Toto čiastkové pole sa skladá iba z jedného triedeného prvku
- Pokračujeme pravou časťou pôvodného poľa, {6, 5, 9}, až kým nezískame konečné zoradené pole
2.2. Výber optimálneho čapu
Kľúčovým bodom v programe QuickSort je výber najlepšieho otočného čapu. Stredný prvok je samozrejme najlepší, pretože by rozdelil zoznam na dva rovnaké čiastkové zoznamy.
Nájsť prostredný prvok z neusporiadaného zoznamu je ale ťažké a časovo náročné, preto považujeme za otočný prvý prvok, posledný prvok, medián alebo akýkoľvek iný náhodný prvok.
3. Implementácia v Jave
Prvá metóda je quickSort () ktorý berie ako parametre pole na triedenie, prvý a posledný index. Najskôr skontrolujeme indexy a pokračujeme, iba ak ešte existujú prvky na triedenie.
Získame index zoradeného pivotu a použijeme ho na rekurzívne volanie oddiel () metóda s rovnakými parametrami ako quickSort () metóda, ale s rôznymi indexmi:
public void quickSort (int arr [], int begin, int end) {if (begin <end) {int partitionIndex = partition (arr, begin, end); quickSort (arr, begin, partitionIndex-1); quickSort (arr, partitionIndex + 1, koniec); }}
Pokračujme oddiel () metóda. Pre zjednodušenie táto funkcia berie ako otočný prvok posledný prvok. Potom skontroluje každý prvok a zamení ho pred pivotom, ak je jeho hodnota menšia.
Na konci rozdelenia sú všetky prvky menšie ako čap naľavo od neho a všetky prvky väčšie ako čap sú napravo od neho. Pivot je na svojej konečnej zoradenej polohe a funkcia vráti túto pozíciu:
súkromný int oddiel (int arr [], int začiatok, int koniec) {int pivot = arr [end]; int i = (začiatok-1); for (int j = begin; j <end; j ++) {if (arr [j] <= pivot) {i ++; int swapTemp = arr [i]; arr [i] = arr [j]; arr [j] = swapTemp; }} int swapTemp = arr [i + 1]; arr [i + 1] = arr [koniec]; arr [end] = swapTemp; návrat i + 1; }
4. Analýza algoritmov
4.1. Časová zložitosť
V najlepšom prípade algoritmus rozdelí zoznam na dva podzoznamy rovnakej veľkosti. Takže prvá iterácia úplnej verzie n-potrebný zoznam O (n). Zvyšné dva podzoznamy sa triedia podľa n / 2 prvky berie 2 * O (n / 2) každý. Vo výsledku má algoritmus QuickSort zložitosť O (n log n).
V najhoršom prípade vyberie algoritmus v každej iterácii iba jeden prvok O (n) + O (n-1) + ... + O (1), ktorá sa rovná O (n2).
V priemere má QuickSort O (n log n) zložitosť, takže je vhodný pre veľké objemy dát.
4.2. QuickSort vs MergeSort
Poďme diskutovať, v ktorých prípadoch by sme si mali zvoliť QuickSort pred MergeSort.
Aj keď programy Quicksort aj Mergesort majú priemernú časovú zložitosť O (n log n), Quicksort je preferovaný algoritmus, pretože má O (log (n)) zložitosť priestoru. Na druhej strane Mergesort vyžaduje O (n) extra úložisko, čo je pre polia dosť drahé.
Quicksort vyžaduje pre svoju činnosť prístup k rôznym indexom, ale tento prístup nie je priamo možný v prepojených zoznamoch, pretože tu nie sú žiadne súvislé bloky; preto pre prístup k prvku musíme iterovať cez každý uzol od začiatku prepojeného zoznamu. Mergesort je tiež implementovaný bez ďalšieho priestoru pre server LinkedLists.
V takom prípade sa všeobecne uprednostňuje zvýšenie réžie pre Quicksort a Mergesort.
5. Záver
Quicksort je elegantný algoritmus triedenia, ktorý je vo väčšine prípadov veľmi užitočný.
Je to všeobecne algoritmus „na mieste“ s priemernou časovou zložitosťou O (n log n).
Ďalším zaujímavým bodom, ktorý treba spomenúť, je Java Arrays.sort () metóda používa Quicksort na triedenie polí primitívov. Implementácia využíva dva otočné čepy a funguje oveľa lepšie ako naše jednoduché riešenie, preto je pre produkčný kód zvyčajne lepšie používať knižničné metódy.
Ako vždy, kód na implementáciu tohto algoritmu nájdete v našom úložisku GitHub.