środa, 5 grudnia 2012

Szybkie sortowanie


Dzisiaj przedstawiony zostanie kolejny algorytm sortowania, konkurencyjne rozwiązanie względem sortowania przez scalanie. Szybkie sortowanie (ang. quick sort) zostało obmyślone przez Tony'ego Hoare i jest uważane za najlepsze w swojej dziedzinie. Podobnie jak sortowanie przez scalanie przeciętna złożoność obliczeniowa tego algorytmu to O(n*log(n)). Odpowiednia implementacja tego algorytmu pozwala zużyć znacznie mniej pamięci niż scalanie: O(log(n) zamiast O(n).
                Algorytm ten również działa na zasadzie "dziel i rządź" - z ciągu elementów wejściowych wybierany jest                 element osiowy (ang. pivot) względem którego będzie się odbywał podział. Polega ono na przeniesieniu wszystkich elementów mniejszych od osi na lewo od niej a większych na prawo. Po takim przetasowaniu element osiowy znajduje się na odpowiedniej dla niego pozycji w ciągu wyjściowym. Następnie, zgodnie z regułą "dziel i rządź" ta sama czynność jest wykonywania na dwóch częściach ciągu wejściowego aż do uzyskania posortowania elementów. Przykład działania znajduje się na obrazku poniżej:
From Wikipedia, the free encyclopedia
               
                Implementacja:
Najlepszym sposobem implementacji algorytmów działających na zasadzie dziel i rządź jest rekurencja. Główna pętla prezentuje się następująco:
void quickSort(int[] arr, int p, int q)
        {
            if (q <= p)
                return;

            int pivotIndex = p + (q - p) / 2;
            if (arr[p] > arr[q])
            {
                if(arr[q] > arr[pivotIndex])
                {
                    pivotIndex = q;
                }
                else if(arr[p] > arr[pivotIndex])
                {
                    pivotIndex = p;
                }
            }
            else
            {
                if(arr[p] > arr[pivotIndex])
                {
                    pivotIndex = p;
                }
                else if (arr[q] < arr[pivotIndex])
                {
                    pivotIndex = q;
                }
            }

            pivotIndex = distribute(arr, p, q, pivotIndex);

            quickSort(arr, p, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, q);
        }

                Argumenty wejściowe funkcji to: tablica z elementami do posortowania, indeks pierwszego elementu tablicy oraz indeks ostatniego elementu. Najpierw sprawdzamy czy możemy dalej dzielić tablicę - jeśli nie, przerywamy rekursję i "wracamy wyżej". Dalej wybierany jest element osiowy - jest on wartością środkową trzech elementów: pierwszego, środkowego i ostatniego. Ten sposób wyboru elementu pivot zwiększa szanse na najbardziej optymalne działanie algorytmu. Dzielenie ciągu następuje poprzez wywołanie funkcji distribute, którą omówimy zaraz a następnie funkcja wywołuje samą siebie aby posortować dalszą część tablicy.
                Sposób implementacji funkcji decyduje o ilości zasobów jaką zużywać będzie sortowanie, omówiony tutaj sposób nie potrzebuje żadnych dodatkowych tablic, gdyż operuje on tylko na tablicy wejściowej.
                int distribute(int[] arr, int p, int q, int pivot)
        {
            int pivotValue = arr[pivot];
            int store = p;

            swap(arr, pivot, q);

            for (int i = p; i < q; i++)
            {
                if (arr[i] < pivotValue)
                {
                    swap(arr, i, store);
                    store++;
                }
            }

            swap(arr, store, q);

            return store;
        }

                Argumenty wejściowe to: tablica na której będą odbywać się operacje, pozycja początku, końca tablicy oraz indeks elementu osiowego. Następnie elementy zostają poprzestawiane zgodnie z regułą: mniejsze na lewo i zostaje zwrócona pozycja środka tego podziału która w późniejszych wywołaniach posłuży jako wskaźnik końca lub początku nowej tablicy.

            Podsumowanie

                Sortowanie szybkie jest niewątpliwie demonem szybkości i oszczędności zasobów ale jest również niezwykle podatne sposób ułożenia danych wejściowych. Quick sort sprawdza się najlepiej gdy dane są czysto losowe schody zaczynają się gdy są one częściowo lub całkowicie posortowane - złożoność obliczeniowa sięga w tych przypadkach nawet O(n2) czyli równie wolno co proste algorytmy pokroju sortowania bąbelkowego. Również wybór elementu osiowego nie jest bez znaczenia: najlepiej gdy ten element jest medianą wszystkich wartości w tablicy, im bliżej wartości minimalnej bądź maksymalnej tym gorzej.

Brak komentarzy:

Prześlij komentarz