Algorytm selekcji Hoare`a
Transkrypt
Algorytm selekcji Hoare`a
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 1.2.1 Wybrane rozwiązania 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: O(n2 ) Przykład dla K = 1: 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: Tn ¬ 1 1 (Tn ) + (T 3N ) + N. 2 2 4 3 Tn ¬ T 3N + 2N. 4 Tn = 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 t d i o . h> #define N 10 int main ( void ) { int A[N+1] = { 0 , 1 0 , 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−−; } e l s e break ; } i f ( J < K) L = I ; i f (K < I ) R = J ; } p r i n t f ( ”%d n a j m n i e j s z y elem en t to : %d\n” , K, A[K ] ) ; return 0 ; } 7