• Nie Znaleziono Wyników

Krok 1: procedura rozdzielania elementów tablicy względem wartości pewnej komórki tablicy służącej za oś podziału; proces sortowania jest dokonywany przez tę właśnie procedurę.

N/A
N/A
Protected

Academic year: 2021

Share "Krok 1: procedura rozdzielania elementów tablicy względem wartości pewnej komórki tablicy służącej za oś podziału; proces sortowania jest dokonywany przez tę właśnie procedurę."

Copied!
19
0
0

Pełen tekst

(1)

Idea:

Jest to również metoda „dziel i rządź”,

ponieważ dzieli tablicę na dwie części, które potem sortuje niezależnie.

Algorytm składa się z dwóch kroków:

Krok 1: procedura rozdzielania elementów tablicy względem wartości pewnej komórki tablicy służącej za oś podziału; proces

sortowania jest dokonywany przez tę właśnie procedurę.

Krok 2: procedura służąca do właściwego sortowania, która nie robi w zasadzie nic oprócz wywoływania samej siebie; zapewnia poskładanie wyników cząstkowych i w

konsekwencji posortowanie całej tablicy.

(2)

Sednem metody jest proces podziału, który zmienia

kolejność elementów w tablicy tak, że spełnione są trzy

warunki:

• element a[i] znajduje się dla pewnego i na właściwej pozycji w tablicy;

•Żaden z elementów a[l], …, a[i-1] nie jest większy niż a[i];

•Żaden z elementów a[i+1],

…, a[r] nie jest mniejszy niż

a[i]. W kółku mamy element

rozgraniczający, elementy mniejsze są na lewo, a większe na prawo.

(3)

Oś podziału

(4)

Implementacja funkcji partition w C++. (W nagłówku funkcji tab jest adresem

pierwszego elementu tablicy A do posortowania, l i r określają odpowiednio początek i koniec podtablicy A, która będzie dzielona przez funkcję partition.)

int partition (int *tab, int l, int r) {

int i=l-1, j=r, v=*(tab+r), s;

for(;;) { i++;

while (*(tab+i)<v) i++;

j--;

while (v<=*(tab+j)) {

if(j==1) break;

j--;

}

if (j==i) break;

s=*(tab+i);

*(tab+i)=*(tab+j);

*(tab+j)=s;

}

s=*(tab+i);

*(tab+i)=*(tab+r);

*(tab+r)=s;

return i;

}

zamiana

//α: 1<=l<r.

//γ: A[k]<v<=A[i] dla k:=1,2,…,i-1.

//δ: A[k]<v<=A[i] dla k:=l,l+1,…i-1

A[j]<v<=A[m] dla m:=j+1,j+2,…, r-1 //μ: A[k]<v dla k:=l,l+1,…i v<=A[m]

dla m:=j,j+1,…, r-1

//η: A[k]<v dla k:=l,l+1,…i-1 v<=A[m] dla m:=i,i+1,…, r-1 bo j=i //β: A[k]<A[i]<=A[m] dla

k:=l,l+1,…i-1 m:=i+1,i+2,…,r War.

początkowy

War.

końcowy

(5)

Przeprowadzimy dowód semantycznej poprawności tego algorytmu, stosując metodę niezmienników.

Warunek γ ma miejsce, bo poprzedzająca go instrukcja „while” zakończy się dla i, przy którym v<=A[i], pozostałe nierówności są wynikiem

działania tej pętli. Jeśli i=1, wtedy pozostałe nierówności nie wystąpią.

Następny warunek δ jest uzupełnieniem γ o podobne jak w warunku γ nierówności A[j]<v<=A[m] dla m:=j+1,j+2,…,r-1, których uzasadnienie przeprowadzamy w oparciu o pętlę „while” ustalającą j. Jeżeli i<j, to

dokonujemy zamiany elementu j-tego z i-tym, co pociąga za sobą zajście μ wobec δ i przejście do następnego przebiegu pętli „for”.

Jeżeli j=i, to wychodzimy z pętli „for” nie dokonując zamiany j-tego

elementu z i-tym, dlatego na wyjściu z pętli „for” ma miejsce warunek η będący warunkiem δ zapisanym dla j=i. Po dokonaniu zamiany

elementu i-tego z r-tym analogicznie jak μ był konsekwencją δ, warunek końcowy β jest konsekwencją η.

(6)

Uzasadnienie, że γ, δ, η, μ, β są niezmiennikami algorytmu partition pozwala na stwierdzenie o indeksie i będącym wartością funkcji

partition: A[i] jest na właściwym miejscu w tablicy uporządkowanej, elementy tablicy od A[l] do A[i-1] są mniejsze od A[i], elementy A[i+1]

do A[r] są większe lub równe A[i]. Zatem algorytm partition jest częściowo poprawny.

Dobra określoność algorytmu jest oczywista.

Posiadanie własności stopu wynika z obserwacji, że obie pętle „while”

zawierają liczniki i oraz j, pierwszy rosnący, ograniczony z góry przez r, drugi malejący ograniczony z dołu przez 1.

Pętla „for” będzie skończona ponieważ dla tak określonych liczników zawsze zajdzie warunek i==j.

(7)

Realizacja w C++

void quicksort (int *tab, int l, int r) {

if(r<=l) return; //1-instrukcja int i=partition(tab, l, r); //2-instrukcja quicksort(tab, l, i-1); //3-instrukcja quicksort(tab, i+1, r); //4-instrukcja }

Właściwe sortowanie

Dowód poprawności:

Własność: Dla dowolnej liczności n=r-l sortowanej tablicy algorytm jest semantycznie poprawny.

Dowód:

Krok1.

Algorytm jest semantycznie poprawny dla n=0. Istotnie, wtedy r=l, a zatem r<=l i z postaci 1-instrukcji wynika, że tablica jednoelementowa nie ulegnie zmianie, pozostanie tablicą uporządkowaną. Oznacza to semantyczną poprawność dla n=0.

(8)

Krok 2.

Ma miejsce następujące twierdzenie: jeżeli algorytm jest semantycznie poprawny dla dowolnych n<=k, gdzie k jest dowolną ustaloną liczbą naturalną nieujemną, to jest semantycznie poprawny dla n=k+1.

Istotnie. Zauważmy, że 1<=k+1, zatem dla n=r-l=k+1 spełniony jest warunek początkowy α algorytmu partition i program wykona poprawnie instrukcję 1 i 2.

Ponieważ l<=i<=r, zatem i-1-l<=r-l-1<=k oraz r-(i+1)=r-i-1<=r-l- 1<=k i na mocy założenia indukcyjnego wywołania algorytmu quicksort w instrukcji 3 i 4 tego algorytmu wykonają się poprawnie, a zatem

wykona się poprawnie cały algorytm.

Tablica będzie uporządkowana, ponieważ instrukcja 3 poprawnie uporządkuje elementy tablicy od od l-tego do i-1-szego, i-ty jest na właściwej pozycji wobec poprawności algorytmu podział , a instrukcja 4 uporządkuje poprawnie elementy tablicy od i+1-szego do r-tego, co kończy dowód twierdzenia w kroku 2.

Wobec spełnienia obu kroków na mocy zasady indukcji matematycznej ma miejsce dowodzona własność.

(9)

Zależy od tego, czy podziały są zrównoważone, czy nie, a to z kolei zależy od tego, które elementy zostaną wybrane do dzielenia.

• Algorytm asymptotycznie ma taką złożoność jak sortowanie przez scalanie.

Podziały zrównoważone

• Algorytm może działać tak wolno jak sortowanie

przez wstawianie.

Podziały

niezrównoważone

(10)

Najgorszy przypadek podziałów:

gdy procedura partition tworzy jeden obszar złożony z n-1 elementów, a drugi tylko z 1 elementu.

Załóżmy, że takie niezrównoważone podziały będą

wykonywane w każdym kroku algorytmu.

(11)

) ( )

1 (

) (

) 1 ( )

1 (

) (

n n

T n

T T

n

  ( )

) (

...

) ( )

1 (

) 2 (

) ( )

1 (

) (

2 2

) 1 ( 1

1

n k

k

n n

n T n

n T n

T

n n n

k n

k

 

 

 

-koszt podziału

-wykonanie dla tablicy jednoelementowej - równanie rekurencyjne

Rozwiązujemy rekurencję, iterując:

Czy to jest pesymistyczna złożoność obliczeniowa?

(12)

Niech Tmax (n) będzie najgorszym czasem działania algorytmu quicksort dla danych wejściowych rozmiaru n. Mamy równanie rekurencyjne:

) ( ))

( )

( (

max )

(

max max

1

max

n

1

T q T n q n

T

q n

   

gdzie parametr q przyjmuje wartości od 1 do n-1, ponieważ mamy

dwa obszary, z których każdy ma co najmniej 1 element.

Zgadujemy, że dla pewnej stałej c. Zatem 2

m ax

( n ) cn

T

) ( )

) (

( max )

( )

) (

( max )

(

2 2

1 1

2 2

1

max

n

1

cq c n q n c q n q n

T

n q n

q

        

2 2

2

2

2 2

2 2

)

2

( ),

1 (

) 1 (

1 ) 1 (

max?

0 2

4 )

( '

1 1

, 2

2 )

( )

(

n n

n

f n

f n

f

q n

q q

f

n q

n nq q

q n q

q f

2 2

2 2

m ax

( n ) c ( 1 ( n 1 ) ) ( n ) cn 2 c ( n 1 ) ( n ) cn

T           

Przy odpowiednim doborze dużej stałej c

(13)

Najlepszy przypadek podziałów:

T(n)=2T(n/2)+Θ(n)

Przypadek 2 tw. o rekurencji

uniwersalnej daje rozwiązanie:

T(n)= Θ(nlgn)

Podział na połowę

(14)

Jeśli zbadamy różnicę między przypadkiem pesymistycznym a najlepszym, to dostaniemy pesymistyczną wrażliwość algorytmu:

Δ(n)=Θ(n2-nlg2n)

(15)

Podziały zrównoważone – przypadek średni

Przykład podziału w stosunku 9 do1

Przypadek średni jest bliski przypadkowi najlepszemu – udowodnimy!

(16)

Podziały zrównoważone – przypadek średni (oczekiwana złożoność) Załóżmy, że

-wystąpienie dowolnej permutacji n liczb całkowitych jako danych do sortowania jest jednakowo prawdopodobne,

- podział permutacji na dwie podtablice i-1 elementową i n-i

elementową jest również jednakowo prawdopodobny dla dowolnego i=1,2,…,n.

Wtedy średnia złożoność obliczeniowa Tsr(n) spełnia warunki:

. ,

) ) (

) 1 ( ( 1

) 1 (

) (

0 ) 1 ( )

0 (

1

n i n i

n T i

T n

n T

T T

n

i

sr sr

sr

sr sr

 

 

   

jednakowo prawdopodobne

. ,

) 1 ( 2

1 )

(

1

n i n i

T n

n T

n

i

sr

sr

 

 

 

 

∙n

(17)

, ) 1 ( 2

) )(

1 (

) 1 (

) 1 (

, ) 1 ( 2

) 1 (

) (

1

1 1

n

i

sr sr

n

i

sr sr

i T n

n n

T n

i T n

n n

nT

odejmujemy

stronami

. 1 1 ,

2 )

1 (

1 ) (

) 1 (

: , 2 ) 1 (

) 1 (

) (

, 2 ) 1 (

2 ) 1 (

) 1 (

) (

 

 

 

n n n

n T n

n T

n n n

n T n

n nT

n n

T n

T n

n nT

sr sr

sr sr

sr sr

sr

Stosujemy iteracje….

. 1 ),

2 / 3 (

2 ) 2 / 3 ) 1 /(

1 ...

3 / 1 2 / 1 1 ( 2

) 1 /(

2 ....

4 / 2 3 / 2 2

) 1 ( 1

2 )

1 (

1 ) (

1

 

 

 

 

n

H n

T n n

n n T n

n T

n sr

sr sr

n+1-sza suma szeregu harmonicznego lub wykorzystanie szacowania sumy

za pomocą całki

(18)

Funkcja harmoniczna:

H

n

 ln n    O ( n

1

),   0 . 57

stała Eulera

) (log 4

. 1 ) / 1 ( log

) 1 log (

) 2

(

2 2

2

n O

n O

n e n

n

T

sr

    

Z całki natomiast:

).

1 (

log 3

) 2 (

) log 1 (

2 ) (

, log 3

) 2 (

2 log 3

) 2 ln(

2 3 1 ln 2 ) 2 ln(

2 3 )

/ 1 ( 2

) 2 / 3 /

1 ( 2 ) 2 / 3 ) 1 /(

1 ...

3 / 1 2 / 1 1 ( 2

2 2

2 2 2

1

1

1

 

 

e n n n

n T

e n n

n x

d x

k n

sr n

n

k

Stąd widać, że

) log

( )

( n O n

2

n

T

sr

(19)

Zalety:

•Praktycznie działa w miejscu (używa tylko niewielkiego stosu pomocniczego).

•Do posortowania n elementów wymaga średnio czasu proporcjonalnego do nlog2n .

•Ma wyjątkowo skromną pętlę wewnętrzną.

Wady:

•Jest niestabilny.

•Zabiera około n2 operacji w przypadku najgorszym.

•Jest wrażliwy (tzn. prosty niezauważony błąd w implementacji może powodować niewłaściwe działanie w przypadku niektórych danych).

Średnia złożoność obliczeniowa jest niemal optymalna. Od kiedy w 1960

C.A.R.Hoare go opublikował, zaczęły się pojawiać jego ulepszone wersje – ale algorytm jest tak zrównoważony, że poprawienie programu w jednym aspekcie, pogarsza jego parametry w innym.

Jest często stosowany w bibliotekach standardowych. Można go usprawnić

ograniczając rekurencję do pewnego ustalonego n, a tablice o mniejszej długości sortujemy nierekurencyjnym algorytmem sortowania.

Cytaty

Powiązane dokumenty

(3) Jeśli w wyznaczonym 2 - elementowym ciągu element prawy jest elementem dodanym lub element prawy jest różny od klucza, nie znaleziono elementu równego

Pokaż przebieg algorytmów wyszukiwania sekwencyjnego (indeks), binarnego z powtórzeniami (kolejne przedziały) oraz bez powtórzeń (kolejne przedziały) przy

Funkcja moŜe otrzymać przez listę parametrów tablice oraz liczbę elementów, natomiast powinna zwracać przez wynik.. (return) nowa

Dalej tworzymy 2 przyciski klikając prawym przyciskiem myszy na szarym polu oraz kursorem najeżdżamy na ikonę Buttons i wybieramy PushButton.. Należy zauważyć, że

Uruchom program, sprawdź efekt podania wartości, która nie występuje w tablicy, wartości występującej w tablicy oraz efekt niepoprawnego podania liczby.. catch z

powyżej podwójnej pętli for, w której tworzymy tablicę w, zadeklaruj trzy zmienne całkowite ld , lu, lz, które posłużą jako liczniki dodatnich, ujemnych i zerowych

Porównaj testy Fishera i M-H dla sondażu, w którym pytano 24 kobiety i 24 mężczyzn, pracujących w bankach, czy mają szansę rozwoju zawodowego.. Odpowiedź TAK uzyskano od 14 kobiet

Wczytaj do dwuwymiarowej tablicy macierz A rozmiaru 3x3, natomiast do jednowymiarowej 3-elementowej tablicy wektor x. Oblicz wynik mnożenia macierzy A przez wektor x,