Algorytmy i struktury danych/Selekcja: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Walen (dyskusja | edycje)
m Zastępowanie tekstu – „,</math>” na „</math>,”
 
(Nie pokazano 32 wersji utworzonych przez 5 użytkowników)
Linia 1: Linia 1:
=Selekcja=
Rozważmy następujący problem:
Rozważmy następujący problem:


Dany jest zbiór <math>A</math> składający się <math>n</math> liczb oraz liczba <math>k</math>.
Dany jest zbiór <math>A</math> składający się z <math>n</math> liczb oraz liczba <math>k</math>.
Należy wyznaczyć <math>k</math>-ty co do wielkości element zbioru <math>A</math>.
Należy wyznaczyć <math>k</math>-ty co do wielkości element zbioru <math>A</math>, tzn.
takie <math>a\in A</math>, że <math>|\{ x\in A : x < a \}|=k-1</math>.


Dla niektórych wartości <math>k</math> (np. 1 lub n) problem jest bardzo prosty i wymaga
Dla niektórych wartości <math>k</math> (np. 1 lub n) problem jest bardzo prosty i wymaga
jedynie wyznaczenia minimalnej lub maksymalnej wartości, co możemy zrobić w czasie <math>O(n)</math>.
jedynie wyznaczenia minimalnej lub maksymalnej wartości w ''A'', co możemy zrobić w czasie <math>O(n)</math>.
Znacznie ciekawszym problemem jest np. znajdowanie mediany, czyli
<math>\lfloor \frac{n}{2} \rfloor</math>-tego co do wielkości elementu.
Tu niestety problem jest bardziej skomplikowany.
 
Możemy zauważyć, że szybkie (np. o złożoności <math>O(n)</math>) znajdowanie mediany
pozwoliłoby tak poprawić algorytm sortowania QuickSort, żeby nawet w pesymistycznym
przypadku działał w czasie <math>O(n\log n)</math>.


Najprostszym rozwiązaniem naszego problemu, może być uporządkowanie
Najprostszym rozwiązaniem naszego problemu może być uporządkowanie
zbioru, np. algorytmem MergeSort i zwrócenie k-tego elementu.
zbioru, np. algorytmem MergeSort, i wskazanie k-tego elementu.
Takie postępowanie wymaga czasu <math>O(n\log n)</math>.
Takie postępowanie wymaga czasu <math>\Omega(n\log n)</math>.


Jednak można łatwo zauważyć, że nasz problem jest znacznie prostszy od problemu sortowania.
Jednak można łatwo zauważyć, że nasz problem jest znacznie prostszy od problemu sortowania.
Czy można rozwiązać go szybciej?
Czy można rozwiązać go szybciej?
Okazuje się, że jest to możliwe, w następnej części wykładu przedstawimy dwa algorytmy,
Okazuje się, że jest to możliwe. W następnej części wykładu przedstawimy dwa algorytmy,
które rozwiązują nasz problem znacznie sprytniej.
które rozwiązują nasz problem znacznie sprytniej.


Linia 25: Linia 35:
algorytm wybiera element dzielący ''m'' (np. pierwszy element z tablicy),
algorytm wybiera element dzielący ''m'' (np. pierwszy element z tablicy),
a następnie używa go do podzielenia tablicy na dwie części.
a następnie używa go do podzielenia tablicy na dwie części.
Do pierszej części tablicy: ''A[1..r]'' zostają przeniesione elementy
Do pierwszej części tablicy - ''A[1..r]'' - zostają przeniesione elementy
o wartościach mniejszych lub równych ''m''.
o wartościach mniejszych lub równych ''m''.
W drugiej części (''A[r+1..n]'') - elementy o wartościach
Do drugiej części (''A[r+1..n]'') - elementy o wartościach
większych lub równych ''m''.
większych lub równych ''m''.


Ponieważ naszym zadaniem jest jedynie wyznaczenie ''k'' tego co do
Ponieważ naszym zadaniem jest jedynie wyznaczenie ''k''-tego co do
wielkości elementu tablicy, możemy zamiast 2 wywołań rekrurencyjnych
wielkości elementu tablicy, możemy zamiast 2 wywołań rekurencyjnych
(jak to było w przypadku algorytmu QuickSort), wykonać jedynie
(jak to było w przypadku algorytmu QuickSort) wykonać tylko
jedno wywołanie rekurencyjne.
jedno wywołanie rekurencyjne.


Jeśli ''k''<=''r'', to poszukiwana wartość znajduje się w pierwszej części
Jeśli <math>k\le r</math>, to poszukiwana wartość znajduje się w pierwszej części
tablicy, wpp. możemy zawęzić poszukiwania do drugiej części tablicy,
tablicy, wpp. możemy zawęzić poszukiwania do drugiej części tablicy,
jednak zamiast wyszukiwać ''k''-tej wartości musimy poszukiwać ''k-r''-tej wartości.
jednak zamiast wyszukiwać ''k''-tej wartości musimy poszukiwać ''(k-r)''-tej wartości.


=== Algorytm ===
=== Algorytm Hoare'a ===


  '''function''' AlgHoara(A[1..n],k);
1 '''function''' AlgHoara(A[1..n],k);
  '''begin'''
2 '''begin'''
  '''if''' n=1 && k=1 '''then''' '''return''' A[1];
'''if''' n=1 '''and''' k=1 '''then''' '''return''' A[1];  
   
  // Partition
  // Partition
m:=A[1]; l:=1; r:=n;
  m:=A[1]; l:=1; r:=n;
'''while'''(l<r) '''do''' '''begin'''
  '''while'''(l<r) '''do''' '''begin'''
7    '''while''' (A[l]<m) '''do''' l++;
    '''while''' (l<n && A[l]<m) '''do''' l++;
8    '''while''' (m<A[r]) '''do''' r--;
    '''while''' (r>1 && A[r]>m) '''do''' r--;
9    '''if''' (l<=r) '''then''' '''begin'''
    '''if''' (l<=r) '''then''' '''begin'''
10      tmp:=A[l]; A[l]:=A[r]; A[r]:=tmp;
      tmp:=A[l]; A[l]:=A[r]; A[r]:=tmp;
11      l++; r--;
      l++; r--;
12    '''end''';
    '''end''';
13  '''end''';
  '''end''';
  14  '''if''' (k<=r) '''then'''  
   
15    '''return''' AlgHoara(A[1..r],k)
  // TODO, sprawdzić czy nie ma błędów   
16  '''else'''
  '''if''' (r<=k) '''then'''  
17    '''return''' AlgHoara(A[r+1..n],k-r)
    '''return''' AlgHoara(A[1..r],k)
  18 '''end''';
  '''else'''
    '''return''' AlgHoara(A[r+1..n],k-r)
  '''end''';


=== Analiza algorytmu ===
=== Analiza algorytmu ===


Niestety w pesymistycznym przypadku ten algorytm może zachowywać się
Niestety, w pesymistycznym przypadku ten algorytm może zachowywać się
bardzo źle.
bardzo źle.
Dla uporządkowanego ciągu i ''k''=''n'', czas działania algorytmu wynosi ''O(n^2)''.
Dla uporządkowanego ciągu i ''k''=''n'' czas działania algorytmu wynosi <math>O(n^2)</math>.
Jednak tak jak w przypadku algorytmu QuickSort, w średni koszt działania algorytmu
Jednak tak jak w przypadku algorytmu QuickSort, w średni koszt działania algorytmu
jest znacznie lepszy i wynosi ''O(n)''.
jest znacznie lepszy i wynosi ''O(n)''.


== Algorytm magicznych piątek ==


* TODO -- dodać kod programu z implementacją
Algorytm Hoare'a w pesymistycznym przypadku może wymagać bardzo długiego czasu
działania. Możemy tak zmodyfikować poprzedni algorytm, aby zapewnić
liniowy czas działania nawet w najgorszym przypadku.


== Algorytm magicznych piątek ==
Kluczem do nowego algorytmu jest lepszy
wybór elementu dzielącego (zmienna ''m'' z 5-linii algorytmu Hoare'a).
Element ten jest obliczany w następujący sposób:
* dzielimy ciąg <math>A</math> na podciągi 5-elementowe <math>P_1,\ldots,P_{\lceil |A|/5 \rceil}</math>, <br><br>
 
----
 
<center>[[Grafika:selekcja.1.png]]</center>
 
----
 
* każdy z podciągów sortujemy, otrzymując <math>P'_1,\ldots,P'_{\lceil |A|/5 \rceil}</math>,,
* wybieramy z każdego podciągu 3-ci co do wielkości element otrzymując krótszy ciąg <math>M</math>, <br><br>
 
----
 
<center>[[Grafika:selekcja.2.png]]</center>


* opis algorytmu,
----
* analiza,
* kod programu z implementacją


* jako ''m'' wybieramy medianę ciągu <math>M</math> (którą to obliczamy rekrurencyjnie).
Dzięki takiemu znacznie bardziej skomplikowanemu wyborowi możemy zagwarantować bardziej
równomierny podział ciągu <math>A</math> na podciągi elementów mniejszych (<math>A_{<}</math>), oraz
większych (<math>A_{>}</math>) od ''m''.


=== Algorytm ===
=== Algorytm ===
Linia 90: Linia 118:
   '''else''' '''begin'''
   '''else''' '''begin'''
       podziel elementy z tablicy ''A'' na podciągi 5-elementowe: <math>P_1,\ldots,P_{n/5}</math>
       podziel elementy z tablicy ''A'' na podciągi 5-elementowe: <math>P_1,\ldots,P_{n/5}</math>
       (jeśli n nie jest wielokrotnością 5, to uzupełnij ostatni podciąg wartościami <math>+\infty</math>)
       (jeśli n nie jest wielokrotnością 5, uzupełnij ostatni podciąg wartościami <math>+\infty</math>)
       Niech <math>M=\{ P_i [3] : 1 \le i \le n/5 \}</math> (zbiór median ciągów <math>P_i</math>);
       Niech <math>M=\{ P_i [3] : 1 \le i \le n/5 \}</math> (zbiór median ciągów <math>P_i</math>);
       m:=AlgorytmMagicznychPiatek(M, <math>\lceil |M|/2 \rceil</math>);
       m:=AlgorytmMagicznychPiatek(M, <math>\lceil |M|/2 \rceil</math>);
Linia 101: Linia 129:
         '''return''' m
         '''return''' m
       '''else'''
       '''else'''
         '''return''' AlgorytmMagicznychPiatek(<math>A_{>}</math>, <math>k-|A_{<}|+|A_=|</math>);
         '''return''' AlgorytmMagicznychPiatek(<math>A_{>}</math>, <math>k-|A_{<}|-|A_=|</math>);
   '''end'''
   '''end'''
  '''end'''
  '''end'''
Linia 107: Linia 135:
=== Analiza złożoności czasowej ===
=== Analiza złożoności czasowej ===


Na pierwszy rzut oka algorytm wygląda bardzo podobnie do algorytmu Hoare'a.
Nowy algorytm jest jednak znacznie bardziej efektywny: nawet w pesymistycznym
przypadku algorytm kończy działanie po <math>O(n)</math> krokach.
Złożoność algorytmu możemy opisać następującym równaniem rekurencyjnym.
<center>
<math>T(n)=O(n)+T(|M|)+max(T(|A_{<}|),\ T(|A_{>}|))</math>
</center>
* rozmiar zbioru |M| możemy ograniczyć przez <math>\lceil n/5 \rceil</math>
* ze względu na dodatkowy czas poświęcony na obliczanie mediany ''m'', możemy podać lepsze ograniczenia na rozmiary zbiorów <math>A_{<},\ A_{>}</math>:
<center>
<math>0 \le A_{<},\ A_{>}\le n - 3\cdot \lfloor |M|/2\rfloor \le 3n/4</math>
</center>
----
<center>[[Grafika:selekcja.3.png]]</center>
----


<math>T(n)=O(n)+T(n/5)+T(3n/4)</math>
<center>
<math>T(n)\le O(n)+T(n/5)+T(3n/4)</math>
</center>


----
----


[[Algorytmy_i_struktury_danych|powrót do wykładu]]
[[Algorytmy_i_struktury_danych|powrót do strony przedmiotu]]

Aktualna wersja na dzień 09:34, 5 wrz 2023

Selekcja

Rozważmy następujący problem:

Dany jest zbiór A składający się z n liczb oraz liczba k. Należy wyznaczyć k-ty co do wielkości element zbioru A, tzn. takie aA, że |{xA:x<a}|=k1.

Dla niektórych wartości k (np. 1 lub n) problem jest bardzo prosty i wymaga jedynie wyznaczenia minimalnej lub maksymalnej wartości w A, co możemy zrobić w czasie O(n). Znacznie ciekawszym problemem jest np. znajdowanie mediany, czyli n2-tego co do wielkości elementu. Tu niestety problem jest bardziej skomplikowany.

Możemy zauważyć, że szybkie (np. o złożoności O(n)) znajdowanie mediany pozwoliłoby tak poprawić algorytm sortowania QuickSort, żeby nawet w pesymistycznym przypadku działał w czasie O(nlogn).

Najprostszym rozwiązaniem naszego problemu może być uporządkowanie zbioru, np. algorytmem MergeSort, i wskazanie k-tego elementu. Takie postępowanie wymaga czasu Ω(nlogn).

Jednak można łatwo zauważyć, że nasz problem jest znacznie prostszy od problemu sortowania. Czy można rozwiązać go szybciej? Okazuje się, że jest to możliwe. W następnej części wykładu przedstawimy dwa algorytmy, które rozwiązują nasz problem znacznie sprytniej.


Algorytm Hoare'a

Algorytm jest oparty na tym samym pomyśle, co algorytm sortowania QuickSort.

Dla danej tablicy A[1..n] oraz liczby k, algorytm wybiera element dzielący m (np. pierwszy element z tablicy), a następnie używa go do podzielenia tablicy na dwie części. Do pierwszej części tablicy - A[1..r] - zostają przeniesione elementy o wartościach mniejszych lub równych m. Do drugiej części (A[r+1..n]) - elementy o wartościach większych lub równych m.

Ponieważ naszym zadaniem jest jedynie wyznaczenie k-tego co do wielkości elementu tablicy, możemy zamiast 2 wywołań rekurencyjnych (jak to było w przypadku algorytmu QuickSort) wykonać tylko jedno wywołanie rekurencyjne.

Jeśli kr, to poszukiwana wartość znajduje się w pierwszej części tablicy, wpp. możemy zawęzić poszukiwania do drugiej części tablicy, jednak zamiast wyszukiwać k-tej wartości musimy poszukiwać (k-r)-tej wartości.

Algorytm Hoare'a

1  function AlgHoara(A[1..n],k);
2  begin
3   if n=1 and k=1 then return A[1]; 
4   // Partition
5   m:=A[1]; l:=1; r:=n;
6   while(l<r) do begin
7     while (A[l]<m) do l++;
8     while (m<A[r]) do r--;
9     if (l<=r) then begin
10      tmp:=A[l]; A[l]:=A[r]; A[r]:=tmp;
11      l++; r--;
12    end;
13  end;
14  if (k<=r) then 
15    return AlgHoara(A[1..r],k)
16  else
17    return AlgHoara(A[r+1..n],k-r)
18 end;

Analiza algorytmu

Niestety, w pesymistycznym przypadku ten algorytm może zachowywać się bardzo źle. Dla uporządkowanego ciągu i k=n czas działania algorytmu wynosi O(n2). Jednak tak jak w przypadku algorytmu QuickSort, w średni koszt działania algorytmu jest znacznie lepszy i wynosi O(n).

Algorytm magicznych piątek

Algorytm Hoare'a w pesymistycznym przypadku może wymagać bardzo długiego czasu działania. Możemy tak zmodyfikować poprzedni algorytm, aby zapewnić liniowy czas działania nawet w najgorszym przypadku.

Kluczem do nowego algorytmu jest lepszy wybór elementu dzielącego (zmienna m z 5-linii algorytmu Hoare'a). Element ten jest obliczany w następujący sposób:

  • dzielimy ciąg A na podciągi 5-elementowe P1,,P|A|/5,



  • każdy z podciągów sortujemy, otrzymując P'1,,P'|A|/5,,
  • wybieramy z każdego podciągu 3-ci co do wielkości element otrzymując krótszy ciąg M,



  • jako m wybieramy medianę ciągu M (którą to obliczamy rekrurencyjnie).

Dzięki takiemu znacznie bardziej skomplikowanemu wyborowi możemy zagwarantować bardziej równomierny podział ciągu A na podciągi elementów mniejszych (A<), oraz większych (A>) od m.

Algorytm

function AlgorytmMagicznychPiatek(A[1..n], k);
begin
  if n<=10 then
     posortuj tablicę A
     return A[k]
  else begin
     podziel elementy z tablicy A na podciągi 5-elementowe: P1,,Pn/5
     (jeśli n nie jest wielokrotnością 5, uzupełnij ostatni podciąg wartościami +)
     Niech M={Pi[3]:1in/5} (zbiór median ciągów Pi);
     m:=AlgorytmMagicznychPiatek(M, |M|/2);
     A<:={A[i]:A[i]<m};
     A=:={A[i]:A[i]=m};
     A>:={A[i]:A[i]>m};
     if |A<|k then
       return AlgorytmMagicznychPiatek(A<, k)
     else if |A<|+|A=|k
       return m
     else
       return AlgorytmMagicznychPiatek(A>, k|A<||A=|);
  end
end

Analiza złożoności czasowej

Na pierwszy rzut oka algorytm wygląda bardzo podobnie do algorytmu Hoare'a. Nowy algorytm jest jednak znacznie bardziej efektywny: nawet w pesymistycznym przypadku algorytm kończy działanie po O(n) krokach.

Złożoność algorytmu możemy opisać następującym równaniem rekurencyjnym.

T(n)=O(n)+T(|M|)+max(T(|A<|), T(|A>|))

  • rozmiar zbioru |M| możemy ograniczyć przez n/5
  • ze względu na dodatkowy czas poświęcony na obliczanie mediany m, możemy podać lepsze ograniczenia na rozmiary zbiorów A<, A>:

0A<, A>n3|M|/23n/4



T(n)O(n)+T(n/5)+T(3n/4)


powrót do strony przedmiotu