Algorytm selekcji Hoare a Łukasz Miemus 1 lutego 2006
Rozdział 1 O algorytmie 1.1 Problem Mamy tablicę A[N] różnych elementów i zmienną int K, takie że 1 K N. Oczekiwane rozwiązanie to określenie K-tego najmniejszego elementu tablicy A[] w taki sposób, aby element ten znalazł się na pozycji A[K] w tablicy oraz wszystkie elementy o indeksach mniejszych od K były mniejsze wartościami od A[K], natomiast elementy od indeksach większych od K były od A[K] większe wartościami: A[1], A[2],..., A[K-1] A[K] A[K+1],..., A[N] 1.2 Wybrane rozwiązania 1.2.1 Algorytm naiwny Algorytm ten opiera się na początkowym posortowaniu tablicy A[], wtedy znalezienie K-tego najmniejszego elementu przeprowadzamy w czasie O(1), a więc złożoność algorytmu wynosi: Sortowanie elementów tablicy (merge-sort lub heapsort) O(n log n) Zwrócenie K-tego elementu O(1) O(n log n), co wydaje się być wynikiem zadowalającym natomiast my pokażemy, że problem ten da się rozwiązać w średnim czasie O(n), stosując algorytm selekcji Hoare a. 1.2.2 Algorytm Hoare a Definicja: K-ty najmniejszy z N elementów to unikalny element, który jest większy od K-1 innych elementów, ale mniejszy od N-K elmentów. 1
Wniosek: Element nie może być K-tym najmniejszym jeśli jest większy od K (i więcej) elementów lub mniejszy od N-K+1 (i więcej) elementów. Zaczynamy od hipotezy: A[K] jest K-tym najmniejszym elementem. Algorytm dzieli tablicę A na dwie części poprzez skanowanie jej od lewej strony (dla indeksów I = 1,2,...) w poszukiwaniu elementu A[I] A[K], skanowanie od prawej strony (dla indeksów J = N,N-1,...) w poszukiwaniu elementu A[J] A[K], oraz zamianę znalezionych elementów; procedura jest powtarzana dopóki wskaźniki I oraz J się nie spotkają. Daje nam to trzy przypadki: 1. (J < K < I) K-ty najmniejszy element jest na wyjściowej pozycji i procedura kończy działanie. 2. (J < I K) Elementy A[1]...A[J] są mniejsze od N-K+1 innych elementów, zatem żaden z nich nie może być K-tym najmniejszym, przeglądniemy więc podtablicę A[I]...A[N] w poszukiwaniu (K-I+1)-tego najmniejszego elementu. 2
3. (K J < I) Elementy A[I]...A[N] są większe od K innych elementów, zatem żaden z nich nie może być K-tym najmniejszym, przeglądniemy więc podtablicę A[1]...A[J] w poszukiwaniu K-tego najmniejszego elementu. 1.2.3 Analiza złożoności algorytmu Hoare a Złożoność pesymistyczna Dla podanej wersji algorytmu, najgorszy z możliwych przypadków to trafienie, jako elementu podziału, największego lub najmniejszego wartością elementu z tablicy A. Powodem jest strategia wyboru w naszym przypadku elementem tym jest po prostu element o K-tym indeksie przeszukiwanej tablicy. Istnieje zatem określony zestaw danych, dla których algorytm zawsze będzie działał w czasie pesymistycznym: Przykład dla K = 1: O(n 2 ) A[1] = N, A[2] = 2, A[3] = 1, A[4] = 3,..., A[N] = N-1 Aby uniknąć takiej sytuacji, można skorzystać z innej wersji algorytmu wybierać za każdym razem element podziału losowo. Należy jednak mieć na uwadze fakt, że nie zmieni to rzędu złożoności! Złożoność oczekiwana Oczekiwany wynik wyboru elementu podziału można rozbić na dwa przypadki. W pierwszym przeszukujemy podtablicę o wielkości co najwyżej 3N 4, w drugim (pesymistycznie) podtablicę o wielkości N. Prawdopodobieństwo wystąpienia dla każdego z tych wyników wynosi 50%, zatem mamy: T n 1 2 (T n) + 1 2 (T 3N 4 ) + N. 3
T n T 3N 4 + 2N. T n = O(n) algorytm działa w czasie liniowym. 4
Rozdział 2 Przykład Ilustracje przedstawiają wszystkie wywołania procedury dla danych zapisanych w tablicy A[10], poszukiwanym elementem jest 6-ty najmniejszy. 1. Procedurę wywołujemy na całej tablicy wejściowej. 2. Elementem, względem którego będzie wykonywany podział, jest A[6] = 4. Ponieważ elementy A[1]...A[J] są mniejsze od N-K+1 (= 5) innych elementów, żaden z nich nie może być poszukiwanym przez nas 6-tym najmniejszym zatem wywołamy procedurę ponownie na podtablicy A[I]...A[N], szukając (K-I+1 = 2)-tego najmniejszego elementu. 3. Elementem podziału jest tym razem A[2] = 8. Ponieważ każdy z elementów A[I]...A[N] jest większy od co najmniej K (= 2) innych elementów, żaden z nich nie może być poszukiwanym 2-gim najmniejszym wywołujemy procedurę na podtablicy A[1]...A[J], szukając (K = 2)- tego najmniejszego elementu. 5
4. Dzielimy względem A[2] = 5. Widać, że na powstałej podtablicy żaden podział nie ma już sensu, 6-ty najmniejszy element został odnaleziony jest nim A[6] = 6, program kończy działanie. 6
Rozdział 3 Implementacja #include <s tdio. h> #define N 10 int main ( void ) { int A[N+1] = {0,10,8,3,1,9,4,6,5,2,7}; int K = 6; int L = 1, R = N; int I, J, X; while (L < R) { X = A[K] ; I = L, J = R; while (1) { while (A[ I ] < X) I++; while (X < A[ J ] ) J ; i f ( I <= J ) { int W = A[ I ] ; A[ I ] = A[ J ] ; A[ J ] = W; I++; J ; } else break ; } i f ( J < K) L = I ; i f (K < I ) R = J ; } p r i n t f ( %d najmniejszy element to : %d\n, K, A[K] ) ; } return 0; 7