Podobnie jak sortowanie przez scalanie, QuickSort jest algorytmem dziel i rządź. Wybiera element jako pivot i dzieli daną tablicę wokół wybranego pivota. Istnieje wiele różnych wersji quickSort, które wybierają punkt zwrotny na różne sposoby.
- Zawsze wybieraj pierwszy element jako przestawny.
- Zawsze wybieraj ostatni element jako przestawny (zaimplementowane poniżej)
- Wybierz losowy element jako przestawny.
- Wybierz medianę jako przestawną.
Kluczowym procesem w quickSort jest partycja (). Celem partycji jest, biorąc pod uwagę tablicę i element x tablicy jako przestawienie, umieść x na jego prawidłowej pozycji w posortowanej tablicy i umieść wszystkie mniejsze elementy (mniejsze niż x) przed x, a wszystkie większe elementy (większe niż x) po x. Wszystko to powinno odbywać się w czasie liniowym.
Pseudokod rekurencyjnej funkcji QuickSort:
Algorytm partycji
Istnieje wiele sposobów na wykonanie partycji, następujący pseudokod przyjmuje metodę podaną w książce CLRS. Logika jest prosta, zaczynamy od skrajnego lewego elementu i śledzimy indeks mniejszych (lub równych) elementów jak i. Jeśli podczas przemierzania znajdziemy mniejszy element, zamieniamy aktualny element na arr. W przeciwnym razie ignorujemy bieżący element.
Pseudokod dla partycji ()
Ilustracja partycji ():
Wdrożenie:
Poniżej przedstawiono implementacje QuickSort:
Wynik:
Sorted array:1 5 7 8 9 10
Analiza QuickSort
Ogólnie czas poświęcony QuickSort można zapisać w następujący sposób.
T(n) = T(k) + T(n-k-1) + (n)
Pierwsze dwa terminy dotyczą dwóch wywołań rekurencyjnych, ostatni termin dotyczy procesu partycjonowania. k to liczba elementów, które są mniejsze niż pivot.
Czas potrzebny na QuickSort zależy od tablicy wejściowej i strategii partycji. Oto trzy przypadki.
Najgorszy przypadek: Najgorszy przypadek występuje, gdy proces partycjonowania zawsze wybiera największy lub najmniejszy element jako element przestawny. Jeśli weźmiemy pod uwagę powyższą strategię partycji, w której ostatni element jest zawsze wybierany jako przestawny, najgorszy przypadek miałby miejsce, gdy tablica jest już posortowana w kolejności rosnącej lub malejącej. W najgorszym przypadku następuje powtórzenie.
T(n) = T(0) + T(n-1) + (n)which is equivalent to T(n) = T(n-1) + (n)
Rozwiązanie powyższej powtarzalności to (n2).
Najlepszy przypadek: Najlepszy przypadek występuje, gdy proces partycjonowania zawsze wybiera element środkowy jako oś. W najlepszym przypadku następuje powtórzenie.
T(n) = 2T(n/2) + (n)
Rozwiązaniem powyższej powtarzalności jest (nLogn). Można to rozwiązać za pomocą przypadku 2 twierdzenia głównego.
Przypadek średni:
Aby przeprowadzić analizę przypadku średniego, musimy wziąć pod uwagę wszystkie możliwe permutacje tablicy i obliczyć czas potrzebny każdej permutacji, która nie Wygląda to łatwo.
Średni przypadek można uzyskać, rozważając przypadek, w którym partycja umieszcza O (n / 9) elementów w jednym zestawie, a O (9n / 10) elementów w drugim. Poniżej znajduje się powtórzenie w tym przypadku.
T(n) = T(n/9) + T(9n/10) + (n)
Rozwiązaniem powyższej powtarzalności jest również O (nLogn)
Chociaż w najgorszym przypadku złożoność czasowa QuickSort to O (n2), które to więcej niż wiele innych algorytmów sortowania, takich jak sortowanie przez scalanie i sortowanie na stosie, QuickSort jest w praktyce szybszy, ponieważ jego wewnętrzna pętla może być efektywnie zaimplementowana w większości architektur i większości rzeczywistych danych. QuickSort można zaimplementować na różne sposoby, zmieniając wybór pivota, tak aby najgorszy przypadek rzadko występował dla danego typu danych. Jednak sortowanie przez scalanie jest ogólnie uważane za lepsze, gdy dane są ogromne i przechowywane w pamięci zewnętrznej.
Czy QuickSort jest stabilny?
Domyślna implementacja nie jest stabilna. Jednak każdy algorytm sortowania można ustabilizować, traktując indeksy jako parametr porównawczy.
Czy QuickSort jest na miejscu?
Zgodnie z szeroką definicją algorytmu lokalnego kwalifikuje się on jako algorytm sortowania w miejscu, ponieważ wykorzystuje dodatkowe miejsce tylko do przechowywania wywołań funkcji rekurencyjnych, ale nie do manipulowania danymi wejściowymi.
Jak zaimplementować QuickSort dla list połączonych?
QuickSort na liście pojedynczo połączonej
QuickSort na podwójnie połączonej liście
Czy możemy zaimplementować QuickSort iteracyjnie?
Tak, zapoznaj się z Iteracyjnym szybkim sortowaniem.
Dlaczego szybkie sortowanie jest preferowane zamiast MergeSort do sortowania tablic
Szybkie sortowanie w swojej ogólnej postaci jest sortowaniem w miejscu (tj. nie wymaga dodatkowej pamięci), podczas gdy sortowanie przez scalanie wymaga O (N) dodatkowej pamięci, N oznacza rozmiar tablicy, która może być dość droga. Przydzielanie i cofanie alokacji dodatkowego miejsca używanego do sortowania przez scalanie wydłuża czas działania algorytmu. Porównując średnią złożoność, stwierdzamy, że oba typy sortów mają średnią złożoność O (NlogN), ale stałe się różnią. W przypadku tablic sortowanie przez scalanie traci z powodu wykorzystania dodatkowej przestrzeni dyskowej O (N).
Większość praktycznych implementacji szybkiego sortowania używa wersji losowej. Wersja randomizowana ma oczekiwaną złożoność czasową O (nLogn).Najgorszy przypadek jest możliwy również w wersji losowej, ale najgorszy przypadek nie występuje dla określonego wzorca (takiego jak posortowana tablica), a losowe szybkie sortowanie działa dobrze w praktyce.
Szybkie sortowanie jest również sortowaniem przyjaznym dla pamięci podręcznej algorytm, ponieważ ma dobrą lokalność odniesienia, gdy jest używany do tablic.
Szybkie sortowanie jest również rekurencyjne, dlatego optymalizacja wywołań ogonowych jest wykonywana.
Dlaczego MergeSort jest preferowany zamiast QuickSort dla połączonych list ?
W przypadku list połączonych przypadek jest inny, głównie ze względu na różnicę w alokacji pamięci tablic i list połączonych. W przeciwieństwie do tablic, połączone węzły list mogą nie sąsiadować ze sobą w pamięci. W przeciwieństwie do tablicy, w liście połączonej możemy wstawiać elementy w środku w O (1) dodatkowej przestrzeni i O (1) czasie. Dlatego operacja scalania typu merge sort może być implementowana bez dodatkowego miejsca dla połączonych list.
W tablicach możemy uzyskać dostęp losowy, ponieważ elementy są ciągłe w pamięci. Powiedzmy, że mamy całkowitą (4-bajtową) tablicę A i niech adres A będzie x, a następnie, aby uzyskać dostęp do A, możemy bezpośrednio uzyskać dostęp do pamięci w (x + i * 4). W przeciwieństwie do tablic nie możemy uzyskać dostępu swobodnego w połączonej liście. Szybkie sortowanie wymaga wielu tego rodzaju dostępu. Na liście połączonej, aby uzyskać dostęp do i-tego indeksu, musimy przemierzyć każdy węzeł od głowy do i-tego węzła, ponieważ nie mamy ciągłego bloku pamięci. W związku z tym zwiększa się koszt szybkiego sortowania. Sortowanie przez scalanie zapewnia dostęp do danych sekwencyjnie, a potrzeba dostępu swobodnego jest niewielka.
Jak zoptymalizować QuickSort, aby zajmował O (Log n) dodatkowej przestrzeni w najgorszym przypadku?
Zobacz Optymalizacja połączeń QuickSort Tail (Zmniejszanie miejsca w najgorszym przypadku do Log n)
Migawki:
- Quiz w QuickSort
- Najnowsze artykuły w QuickSort
- Praktyka kodowania dla sortowanie.
Inne algorytmy sortowania w GeeksforGeeks / GeeksQuiz:
Sortowanie przez wybór, Sortowanie bąbelkowe, Sortowanie przez wstawianie, Sortowanie przez scalanie, Sortowanie na stosie, Szybkie sortowanie , Sortowanie radix, sortowanie zliczaniem, sortowanie kubełkowe, sortowanie skorupowe, sortowanie grzebieniowe, sortowanie dziurkowe