Informatyka dla klas II - Wyszukiwanie k-tego największego elementu w zbiorze

Proste rozwiązanie tego problemu jest następujące:

 

Przygotowujemy pusty zbiór M, w którym będziemy składowali k największych elementów. Przeglądamy zbiór T i kolejne elementy próbujemy wstawić do M. Element zbioru T trafia do M wtedy, gdy zbiór M nie jest jeszcze zapełniony lub gdy element zbioru T jest większy od pierwszego elementu zbioru M. Elementy wstawiamy tak, aby zbiór M był uporządkowany nierosnąco. Gdy zakończymy przeglądanie zbioru T w pierwszym elemencie zbioru M będzie k-ty największy element zbioru T.

 

Wyjaśnienia wymaga sposób działania na zbiorze M. Odwzorujemy go w tablicy (k + 1) elementowej. Do wstawiania elementów wykorzystamy ideę wartowników, którą opisaliśmy w rozdziale o wyszukiwaniu liniowym z wartownikiem. Tablicę M wypełnimy od M[0] do M[k-1] najmniejszą liczbą całkowitą – zagwarantuje to wstawianie najmniejszych elementu zbioru Z, jeśli w M jest jeszcze miejsce. Na ostatniej pozycji M[k] umieścimy największą liczbę całkowitą – zagwarantuje nam ona, iż wstawiane elementy nie wyjdą poza k-1 pozycję.

 

Algorytm wyszukiwania k-tego największego elementu – wersja nr 1

Wejście
n  –  liczba elementów w tablicy T, n > 0, n N
T  – n-elementowa tablica, w której poszukujemy k-tej największej wartości
k  – określa numer największej wartości, której szukamy w T, k > 0, k n, k N
Wyjście:

k-ta największa wartość elementu w tablicy T. Dodatkowo M[0]...M[k-1] tworzy ciąg wartości maksymalnych z tablicy T.

Elementy pomocnicze:
M  –  k + 1 elementowa tablica do przechowywania wartości maksymalnych z T
i,j  – indeksy w tablicach T i M, i,j C
Lista kroków:
K01: Dla i = 0,1,...,k - 1, wykonuj K02 ; inicjujemy tablicę M
K02:     M[i] ← najmniejsza liczba całkowita  
K03: M[k] ← największa liczba całkowita  
K04: Dla i = 0,1,...,n-1 wykonuj K05...K10 ; przeglądamy kolejne elementy T
K05:     xT[i] ; zapamiętujemy i-ty element T
K06:     j ← -1  
K07:     Dopóki x > M[j+1] wykonuj K08...K09 ; szukamy miejsca dla x w M
K08:         jj + 1 ; przesuwamy się na następną pozycję w M
K09:         M[j] ← M[j+1] ; przesuwamy elementy, aby zrobić miejsce dla x
K10:     Jeśli j ≥ 0, to M[j] ← x ; wstawiamy element x do M
K11: Zakończ z wynikiem M[0] ; w M[0] jest k-ty największy element

 

Jeśli zbiór T jest bardzo duży lecz wartości jego elementów tworzą względnie mały przedział liczb całkowitych, to do wyznaczenia k-tego największego elementu można wykorzystać następujące rozwiązanie:

 

Tworzymy tablicę L posiadającą tyle elementów, ile liczb całkowitych zawiera przedział <minT,maxT> (wartość minimalna i maksymalna elementów zbioru T). Elementy L zerujemy – będą one pełniły rolę liczników elementów zbioru T. Teraz przeglądamy zbiór T od pierwszego do ostatniego elementu i odpowiednio zliczamy napotkane elementy T w licznikach L. Na koniec przeglądamy liczniki L od ostatniego (odpowiada maxT) do pierwszego (odpowiada minT). Ponieważ licznik L[i] zawiera informację o tym, ile razy wartość i występuje w T, to przy każdym kolejnym liczniku odejmujemy jego stan od k. Jeśli k wyzeruje się lub przyjmie wartość ujemną, to poszukiwana wartość jest równa indeksowi licznika, który spowodował tę sytuację.

 

Klasa złożoności obliczeniowej tak zdefiniowanego algorytmu wynosi O(n + m), gdzie n oznacza liczbę elementów w zbiorze T, a m liczbę ich możliwych wartości. Dodatkowo algorytm wymaga O(m) komórek pamięci na przechowanie liczników. Musimy również znać zakres wartości elementów zbioru T – można tutaj wykorzystać algorytm jednoczesnego wyszukiwania min i max.

 

Algorytm wyszukiwania k-tego największego elementu – wersja nr 2

Wejście
n  –  liczba elementów w tablicy T, n > 0, n N
T  – n-elementowa tablica, w której poszukujemy k-tej największej wartości
k  – określa numer największej wartości, której szukamy w T, k > 0, k n, k N
minT  – minimalna wartość w T
maxT  – maksymalna wartość w T
Wyjście:

k-ta największa wartość elementu w tablicy T.

Elementy pomocnicze:
L  –  tablica liczników. Elementy są liczbami całkowitymi.
i  – indeksy w tablicach T i L, i C
m  – liczba wartości elementów T,  m N
Lista kroków:
K01: mmaxT - minT + 1 ; przygotowujemy tablicę liczników
K02: Twórz L o m elementach  
K03: Dla i = 0,1,...,m-1 wykonuj K04  
K04:     L[i] ← 0 ; zerujemy liczniki wartości
K05: Dla i = 0,1,...,n-1 wykonuj K06 ; zliczamy wartości elementów T
K06:     Zwiększ o 1 L[T[i] - minT]  
K07: im - 1 ; szukamy k-tego największego elementu
K08: Dopóki k > 0 wykonuj K09...K10  
K09:     kk - L[i] ; od k odejmujemy liczbę elementów o wartości i
K10:     ii - 1 ; przechodzimy do niższej wartości
K11: Zakończ z wynikiem i + 1 + minT  

 

Przykładowe dane do ćwiczeń

Pierwsza liczba to n – ilość elementów w tablicy T. Druga liczba to k – numer największej wartości. Następne n liczb to zawartość tablicy T.

40 10
779 147 424 546 363 390 95 226 179 440 576 347 15 271 860 362 870 290 476 453
594 382 782 189 369 965 622 44 579 350 968 622 775 924 250 982 7 667 733 149
484 665 40 452 26 296 300 379 206 173 215 696 61 34 120 255 460 332 961 740
123 349 604 435 859 299 189 813 471 593 335 899 976 791 190 209 529 954 437 750

 

Szybkie wyszukiwanie k-tego największego elementu

Jeśli nie musimy zachowywać oryginalnej kolejności elementów w zbiorze T, to istnieje szybki algorytm znajdowania k-tego największego elementu, który posiada oczekiwaną klasę złożoności obliczeniowej równą O(n log n) (liniowo logarytmiczna). Algorytm ten nosi nazwę Szybkiego Wyszukiwania (ang. Quick Select) i został opracowany przez profesora Tony'ego Hoare'a, twórcę jednego z najszybszych algorytmów sortujących – Sortowania Szybkiego (ang. Quick Sort).

Działanie algorytmu Szybkiego Wyszukiwania oparte jest na zasadzie Dziel i Zwyciężaj (ang. Divide and Conquer). Polega ona na rekurencyjnym podziale pierwotnego problemu na problemy prostsze tego samego typu. Podział wykonywany jest dotąd, aż rozwiązanie stanie się oczywiste. Następnie z rozwiązań podproblemów tworzymy rozwiązania na wyższych poziomach aż dojdziemy do rozwiązania problemu pierwotnego

W przypadku Szybkiego Wyszukiwania postępujemy w sposób następujący:

 

W zbiorze T wybieramy dowolny element. Oznaczmy go przez v i nazwijmy elementem zwrotnym (ang. pivot). Następnie dokonujemy podziału zbioru T na dwa podzbiory TL i TP (lewy i prawy). W podzbiorze TP powinny znaleźć się elementy o wartościach nie większych od v. Z kolei w podzbiorze TP powinny być elementy o wartościach nie mniejszych od v. Sam element v musi być pierwszym elementem podzbioru TP. Po takim podziale sprawdzamy, czy v jest (n - k)-tym elementem zbioru T. Jeśli tak, to v jest k-tym największym elementem w tym zbiorze. Jeśli nie, to za nowy zbiór do podziału przyjmujemy ten z podzbiorów TL lub TP, w którym występuje pozycja (n - k)-ta i całą procedurę powtarzamy aż do znalezienia k-tego największego elementu.

 

Podział zbioru na dwie partycje

Podstawowym problemem w algorytmie Szybkiego Wyszukiwania jest podział zbioru na dwa podzbiory, partycje, o wymaganych własnościach. Ponieważ zbiór T będziemy odwzorowywali w tablicy n-elementowej T, to zdefiniujmy dwie zmienne, które będą przechowywały indeksy pierwszego i końcowego elementu podzbioru:

 

ip – przechowuje indeks pierwszego elementu podzbioru – początek
ik – przechowuje indeks ostatniego elementu podzbioru – koniec

 

Początkowo podzbiór obejmuje cały zbiór T, zatem zmienne te przyjmują odpowiednio wartości:

 

ip = 0
ik = n - 1, gdzie n jest liczbą elementów tablicy T

 

Odwzorowanie zbioru T w tablicy T
 T[0]   T[1]   T[2]   T[3]    ...     ...     ...     ...   T[n-3] T[n-2] T[n-1]
ip   ik

 

Element zwrotny można wybierać na różne sposoby.

  1. Jako pierwszy element partycji, vT[ip]
  2. Jako ostatni element partycji, vT[ik]
  3. Jako element środkowy partycji, vT[(ip + ik) shr 1]
  4. Jako element o losowym indeksie, vT[ip + losowe(ik - ip + 1)]

Poniżej podajemy algorytm partycjonowania zbioru wg pierwszego elementu partycji głównej. Jeśli zechcemy partycjonować wg innego elementu zwrotnego, to po prostu wymieniamy wybrany element zwrotny z pierwszym elementem partycji i dalej wykorzystujemy podany poniżej algorytm.

 

Algorytm partycjonowania zbioru wg pierwszego elementu

Funkcja Dziel_na_partycje(T,ip,ik)
Wejście
T  –  tablica, której podzbiór partycjonujemy. Za ostatnim elementem partycji należy umieścić wartownika o wartości większej od każdego elementu partycji.
ip  – indeks pierwszego elementu partycji, ip C
ik  – indeks końcowego elementu partycji, ik C
Wyjście:

j – pozycja elementu zwrotnego w T. Element ten dzieli partycję wejściową na dwie partycje:


    { T[ip] ... T[j - 1] } – elementy mniejsze lub równe v – podzbiór TL
     
{ T[j] ... T[ik] } – elementy większe lub równe v – podzbiór TP

Elementy pomocnicze:
v  –  wartość elementu zwrotnego
i,j  – indeksy w tablicy T, i,j C
Lista kroków funkcji Dziel_na_partycje(T,ip,ik):
K01: vT[ip] ; zapamiętujemy wartość elementu zwrotnego
K02: iip ; indeksem i będziemy szukali elementów ≥ v
K03: jik + 1 ; indeksem j będziemy szukali elementów ≤ v
K04: Dopóki i < j, wykonuj K05...K09 ; w pętli elementy większe umieszczamy w TP, a mniejsze w TL
K05:     ii + 1 ; przesuwamy się na następną pozycję w TL
K06:     Jeśli T[i] < v, to idź do K05 ; szukamy elementu, który nie należy do TL
K07:     jj - 1 ; przesuwamy się na poprzednią pozycję w TP
K08:     Jeśli T[j] > v, to idź do K07 ; szukamy elementu, który nie należy do TP
K09:     Jeśli i < j, to T[i] T[j] ; znalezione elementy zamieniamy miejscami
K10: T[ip] ← T[j] ; zwalniamy pozycję elementu zwrotnego
K11: T[j] ← v ; na zwolnionej pozycji umieszczamy element zwrotny
K12: Zakończ z wynikiem j ; kończymy zwracając pozycję elementu zwrotnego

 

Algorytm szybkiego wyszukiwania k-tego największego elementu - wersja nr 3

Wejście
n  –  liczba elementów w zbiorze T
T  –  tablica (n+1)-elementowa odwzorowująca zbiór T, w którym poszukujemy k-tego największego elementu. Na pozycji T[n] należy umieścić wartownika o wartości większej od każdego elementu zbioru.
k  – określa numer porządkowy największego elementu do znalezienia w T, k > 0, k N
Wyjście:

Wartość k-tego największego elementu zbioru T.

Elementy pomocnicze:
ip  –  indeks początku partycji, ip C
ik  – indeks końca partycji, ik C
pv  – zawiera pozycję elementu zwrotnego

Dziel_na_partycje(T,ip,ik) – funkcja dzieląca na dwie partycje wg elementu zwrotnego. Funkcja opisana powyżej.

Lista kroków:
K01: ip ← 0 ; startowa partycja obejmuje cały zbiór T
K02: ik n - 1  
K03: pv ← Dziel_na_partycje(T,ip,ik) ; dokonujemy podziału na dwie partycje TL i TP
K04: Jeśli pv = n - k, to zakończ z wynikiem T[pv] ; sprawdzamy, czy znaleźliśmy poszukiwany element
K05: Jeśli n - k < pv, idź do K08 ; jeśli nie, to w zależności od pozycji elementu zwrotnego
K06: ippv + 1 ; elementu będziemy szukać w TP
K07: Idź do K03 ; lub
K08: ikpv - 1 ; elementu będziemy szukać w TL
K09: Idź do K03  

 

 



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.