![]() |
Autor artykułu: mgr Jerzy Wałaszek, wersja1.0 |
©2010 mgr
Jerzy Wałaszek
|
Zadanie polega na wyszukaniu w tablicy T elementu v, od którego w tej tablicy jest dokładnie k - 1 elementów większych. Czyli element v jest k-tym największym elementem w tablicy T. Jeśli nie musimy zachowywać oryginalnej kolejności elementów w zbiorze Z, 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 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.
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
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.
Jako pierwszy element partycji, v ← T[ip]
Jako ostatni element partycji, v ← T[ik]
Jako element środkowy partycji, v ← T[(ip + ik) / 2]
Jako element o losowym indeksie, v ← T[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.
| 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 |
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
| v | - | wartość elementu zwrotnego |
| i,j | - | indeksy w tablicy T[ ], i,j ∈C |
| Krok 1: | v ← T[ip] | ; zapamiętujemy wartość elementu zwrotnego |
| Krok 2: | i ← ip | ; indeksem i będziemy szukali elementów ≥ v |
| Krok 3: | j ← ik + 1 | ; indeksem j będziemy szukali elementów ≤ v |
| Krok 4: | Jeśli i ≥ j, idź do kroku 11 | ; w pętli elementy większe umieszczamy w ZP, a mniejsze w ZL |
| Krok 5: | i ← i + 1 | ; przesuwamy się na następną pozycję w ZL |
| Krok 6: | Jeśli T[i] < v, to idź do kroku 5 | ; szukamy elementu, który nie należy do ZL |
| Krok 7: | j ← j - 1 | ; przesuwamy się na poprzednią pozycję w ZP |
| Krok 8: | Jeśli v < T[j], to idź do kroku 7 | ; szukamy elementu, który nie należy do ZP |
| Krok 9: | Jeśli i < j, to wymień T[i] z T[j] | ; znalezione elementy zamieniamy ze sobą |
| Krok 10: | Idź do kroku 4 | ; kontynuujemy pętlę |
| Krok 11: | T[ip] ← T[j] | ; zwalniamy pozycję elementu zwrotnego |
| Krok 12: | T[j] ← v | ; na zwolnionej pozycji umieszczamy element zwrotny |
| Krok 13: | Zakończ | ; kończymy, j zawiera pozycję elementu zwrotnego |
Przykładowe dane dla programu
Pierwsza liczba określa liczbę elementów n. Następne n liczb całkowitych jest zawartością zbioru. Zbiór jest dzielony na dwie partycje względem pierwszego elementu. W partycji lewej znajdą się elementy mniejsze lub równe pierwszemu elementowi. W partycji prawej znajdą sie elementy wieksze lub równe pierwszemu elementowi.
15
467 221 187 872 378 621 119 187 982 621 193 532 468 333 518
// Podział zbioru na dwie partycje
// (C)2010 I LO w Tarnowie
//------------------------
#include <iostream>
using namespace std;
// Funkcja dokonuje podziału na partycje
// względem pierwszego elementu. Zwraca jako wynik
// pozycję elementu pierwszego po podziale
//------------------------------------------------
int podziel(int * T, int ip, int ik)
{
int v,x,i,j;
v = T[ip]; i = ip; j = ik + 1;
while(i < j)
{
while(T[++i] < v);
while(v < T[--j]);
if(i < j)
{
x = T[i]; T[i] = T[j]; T[j] = x;
}
}
T[ip] = T[j]; T[j] = v;
return j;
}
int main()
{
int * T,n,i,p;
// odczytujemy liczbę elementów
cin >> n;
// tworzymy tablicę dynamiczną o n elementach
T = new int[n + 1];
// wczytujemy elementy do tablicy
for(i = 0; i < n; i++) cin >> T[i];
// umieszczamy strażnika
T[n] = 2147483647;
// dzielimy na partycje
p = podziel(T, 0, n - 1);
// wyświetlamy wyniki
cout << endl << endl;
for(i = 0; i < n; i++)
{
if(i == p) cout << "| ";
cout << T[i] << " ";
}
cout << endl << endl;
// usuwamy tablicę dynamiczną
delete [] T;
return 0;
}
|
| 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 |
Wartość k-tego największego elementu zbioru T.
| ip | - | indeks początku partycji, ip ∈C |
| ik | - | indeks końca partycji, ik ∈C |
| pv | - | zawiera pozycję elementu zwrotnego |
podziel(T[ ],ip,ik) - funkcja dzieląca na dwie partycje wg elementu zwrotnego na pozycji ip.
| Krok 1: | ip ← 0 | ; startowa partycja obejmuje cały zbiór T |
| Krok 2: | ik ← n - 1 | |
| Krok 3: | pv ← podziel(T[ ], ip, ik) | ; dokonujemy podziału na dwie partycje TL i TP |
| Krok 4: | Jeśli pv = n - k, to idź do kroku 10 | ; sprawdzamy, czy znaleźliśmy poszukiwany element |
| Krok 5: | Jeśli pv > n - k to idź do kroku 8 | ; jeśli nie, to w zależności od pozycji elementu zwrotnego |
| Krok 6: | ip ← pv + 1 | ; elementu będziemy szukać w TP |
| Krok 7: | Idź do kroku 3 | ; lub |
| Krok 8: | ik ← pv - 1 | ; elementu będziemy szukać w TL |
| Krok 9: | Idź do kroku 3 | |
| Krok 10: | Pisz T[pv] | ; wyprowadzamy znaleziony element |
| Krok 11: | Zakończ |
Przykładowe dane dla programu
Pierwsza liczba określa k. Druga liczba określa liczbę elementów n. Następne n liczb całkowitych jest zawartością zbioru.
7
30
467 221 187 872 378 621 119 187 982 621 193 532 468 333 518
256 982 342 761 129 481 872 991 120 339 301 491 691 182 571
// Szybkie wyszukiwanie k-tego elementu
// (C)2010 I LO w Tarnowie
//-------------------------------------
#include <iostream>
using namespace std;
// Funkcja dokonuje podziału na partycje
// względem pierwszego elementu. Zwraca jako wynik
// pozycję elementu pierwszego po podziale
//------------------------------------------------
int podziel(int * T, int ip, int ik)
{
int v,x,i,j;
v = T[ip]; i = ip; j = ik + 1;
while(i < j)
{
while(T[++i] < v);
while(v < T[--j]);
if(i < j)
{
x = T[i]; T[i] = T[j]; T[j] = x;
}
}
T[ip] = T[j]; T[j] = v;
return j;
}
int main()
{
int * T,n,k,i,ip,ik,pv;
// odczytujemy k oraz n
cin >> k >> n;
// tworzymy tablicę dynamiczną o n elementach
T = new int[n + 1];
// wczytujemy elementy do tablicy
for(i = 0; i < n; i++) cin >> T[i];
// umieszczamy strażnika
T[n] = 2147483647;
// szukamy k-tego elementu
ip = 0; ik = n - 1;
while(true)
{
pv = podziel(T, ip, ik);
if(pv == n - k) break;
if(pv > n - k) ik = pv - 1;
else ip = pv + 1;
}
// wyświetlamy wyniki
cout << "\n\nk = " << k << endl
<< "v = " << T[pv] << endl << endl;
for(i = 0; i < n; i++)
{
if(i == pv) cout << "*";
cout << T[i] << " ";
}
cout << endl << endl;
// usuwamy tablicę dynamiczną
delete [] T;
return 0;
}
|
Medianą zbioru T nazwiemy wartość v, od której w tym zbiorze jest tyle samo elementów większych lub równych co mniejszych lub równych. Mediana posiada wiele ważnych zastosowań praktycznych w statystyce, grafice, obróbce dźwięku i wielu innych dziedzinach.
Jeśli zbiór T jest posortowany rosnąco, to
przy nieparzystej liczbie elementów n > 1 mediana
jest elementem środkowym T[n/2]
(indeksy elementów rozpoczynają się od 0).
Na przykład dla zbioru T = {1,3,5,8,9}
medianą jest element 5 - poprzedzają go dwa elementy 1 i 3 oraz wyprzedzają
dwa elementy 8 i 9.
przy parzystej liczbie elementów n > 1 mediana
jest średnią arytmetyczną dwóch środkowych elementów T[n/2-1]
i T[n/2].
Na przykład dla zbioru T = {1,3,5,8,9,9} mediana jest równa
Istnieją również pojęcia dolnej mediany
(ang. lower median) i górnej mediany
(upper median), które w tym przypadku oznaczają odpowiednio element T[n/2-1]
i T[n/2] w ciągu uporządkowanym o parzystej liczbie
elementów.
Medianę możemy w prosty sposób znaleźć wykorzystując nasz algorytm szybkiego wyszukiwania k-tego największego elementu. Twoim zadaniem jest napisanie odpowiedniego programu.
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe