O(n) - Szkolnictwo.pl

advertisement
Materiały pochodzą z Platformy Edukacyjnej
Portalu www.szkolnictwo.pl
Wszelkie treści i zasoby edukacyjne publikowane na łamach Portalu www.szkolnictwo.pl mogą być wykorzystywane przez jego Użytkowników wyłącznie
w zakresie własnego użytku osobistego oraz do użytku w szkołach podczas zajęć dydaktycznych. Kopiowanie, wprowadzanie zmian, przesyłanie, publiczne odtwarzanie
i wszelkie wykorzystywanie tych treści do celów komercyjnych jest niedozwolone. Plik można dowolnie modernizować na potrzeby własne oraz do wykorzystania
w szkołach podczas zajęć dydaktycznych.
Algorytmy sortujące
Drzewa decyzyjne.
Sortowanie przez kopcowanie
Drzewo (tree)
W informatyce drzewa są strukturami danych
reprezentującymi drzewa matematyczne. W naturalny
sposób reprezentują hierarchię danych (obiektów
fizycznych i abstrakcyjnych, pojęć, itp.), dlatego głównie
do tego celu są stosowane.
Drzewa ułatwiają i przyspieszają wyszukiwanie, a
także pozwalają w łatwy sposób operować na
posortowanych danych. Posiadają one bardzo duże
znaczenie, są stosowane praktycznie w każdej dziedzinie
informatyki (np. bazy danych, grafika komputerowa,
przetwarzanie tekstu, telekomunikacja).
Drzewo (ang. tree) jest dynamiczną strukturą
zbudowaną z węzłów N lub wierzchołków (ang. node).
Każdy z węzłów może posiadać jednego rodzica (przodka
lub węzła nadrzędnego) (ang. parent node) i kilku
potomków (dzieci lub węzły potomne) (ang. child node).
Węzeł, który nie posiada rodzica nazywamy
węzłem głównym lub korzeniem drzewa R (ang. root
node).
Węzeł, który nie posiada potomka to węzeł
terminalny – liść L (ang. leaf).
Drzewo może posiadać wiele liści lecz tylko jeden
korzeń.
R
N
L
L
L
N
L
Drzewo binarne (binary tree)
Drzewo binarne jest hierarchiczną strukturą
danych. W hierarchii liniowej każdy element
może posiadać co najwyżej jeden element
następnego ciągu tzw. następnik. Każdy węzeł
może posiadać dwa następniki (dzieci) – stąd
nazwa drzewa – binarne to znaczy dwójkowe –
zawierające dwa elementy.
Węzły
są
połączone
krawędziami
symbolizującymi
następstwo
kolejnych
elementów w strukturze drzewa binarnego.
Według rysunku, po prawej stronie węzeł A
posiada dwa węzły potomne: B i C. Węzeł B nosi
nazwę lewego potomka (ang. left child node), a
węzeł C nosi nazwę prawego potomka (ang. right
child node).
Z kolei węzeł B posiada węzły potomne D i
E, a węzeł C ma węzły potomne F i G. Natomiast
liściami są węzły terminalne D, E, F i G.
A
B
D
C
E
F
Przykład drzewa binarnego
G
Drzewo binarne a tablica
Aby móc przetwarzać za pomocą komputera
struktury drzew binarnych musimy zapisać,
przedstawić je w pamięci. W tym celu stosujemy nelementową tablicę (rys. 1) – każdy jej element
reprezentuje jeden węzeł drzewa binarnego. Dla
określenia związku pomiędzy poszczególnymi
indeksami elementów w tablicy a położeniem tych
elementów w strukturze drzewa binarnego
określimy:
• element d[1] będzie korzeniem drzewa
• i-ty poziom drzewa binarnego wymaga 2i-1
węzłów, które będą kolejno pobierane z tablicy
(rys. 2)
W celu określenia k-tego węzła (rys. 3)
zostaną wprowadzone następujące oznaczenia –
dla węzłów potomnych:
• 2k – lewy potomek
• 2k+1 – prawy potomek
• węzeł nadrzędny ma indeks równy [k / 2]
(dzielenie całkowitoliczbowe)
Rys. 1 Tablica
Rys. 2 Drzewo binarne
d[k/2]
d[k]
d[2k]
d[2k+1]
Rys. 3 Drzewo binarne dla k-tego węzła
Przykład 1
Jako przykład zostanie przedstawiony sposób w jaki tworzymy drzewo binarne dla zbioru
liczb [8 6 9 2 5 7 1]
W celu utworzenia drzewa binarnego bierzemy pierwszy element
zbioru, w naszym przypadku 8 – będzie to korzeń
8
8692571
8
6
Kolejnym etapem jest dołączenie do korzenia dwóch węzłów
potomnych – elementów, które w zbiorze znajdują się obok siebie.
Są to 6 i 9.
9
8692571
8
6
Następnie, kolejne dwa elementy ze zbioru dołączamy do lewego
węzła potomnego (6) – będą to jego węzły potomne – liczby 2 i 5.
9
2
5
8692571
8
6
2
Ostatnim etapem jest dołączenie kolejnych elementów tj. 7 i 1 do
prawego węzła. Drzewo jest kompletne.
9
5
7
8692571
1
Ścieżką nazywamy ciąg węzłów drzewa binarnego spełniających warunek, iż
każdy węzeł poprzedni jest rodzicem węzła następnego. Jeśli ścieżka składa się z k
węzłów, to długością ścieżki jest liczba k - 1.
W celu zrozumienia zawiłej definicji zilustrujemy to przykładem:
d[1]
d[2]
d[4]
d[8]
d[3]
d[5]
d[9] d[10]
d[6]
d[11]
d[12]
d[7]
d[13] d[14]
d[15]
Na rysunku została przedstawiona ścieżka biegnąca przez węzły {d[1], d[2], d[5], d[10]} zawiera ona cztery węzły, ma zatem długość równą 3.
Wysokością drzewa binarnego nazwiemy długość najdłuższej ścieżki od korzenia do liścia.
W powyższym przykładzie taka ścieżka ma długość równą 3 – zatem wysokość
przedstawionego drzewa ma wysokość równą 3.
Dla n węzłów zrównoważone drzewo binarne ma wysokość równą:
h = [log2 n]
Kopiec
Kopiec jest drzewem binarnym, które musi spełnić następujący warunek, tzw.
warunek kopca:
Węzeł nadrzędny jest większy lub równy węzłom potomnym
(w porządku malejącym relacja jest odwrotna - mniejszy lub równy).
Własności kopca:
• korzeń zawsze jest największym (w porządku malejącym najmniejszym)
elementem z całego drzewa binarnego
• uporządkowanie - wartość każdego wierzchołka (rodzica) jest większa niż wartości
jego potomków.
• kształt - synowie znajdują się na jednym lub więcej poziomach, a te na najniższym
poziomie (liście) są przesunięte jak najbardziej w lewo. Wynika z tego, że jeżeli
drzewo zawiera n wierzchołków, to żaden z nich nie jest bardziej oddalony od
korzenia niż o (log n) węzłów.
Własności te są warunkami na tyle silnymi, aby umożliwić szybkie odnalezienie
elementu największego lub najmniejszego w zbiorze, a jednocześnie umożliwiają szybką
reorganizację struktury kopca po dodaniu lub usunięciu z niego elementu.
Przykład 2
Utwórzmy kopiec dla elementów danego zbioru liczb [7 6 9 3 4 8 11]
7
7 6 9 3 4 8 11
7
Podobnie jak w przykładzie 1, budowę kopca rozpoczynamy od
pierwszego elementu zbioru, w naszym przypadku liczba 7 będzie
korzeniem.
Do korzenia dołączamy kolejny element zbioru. Warunek kopca
jest spełniony
6
7 6 9 3 4 7 11
7
6
9
7 6 9 3 4 7 11
7
6
9
Ten krok ma na celu przywrócenie warunku kopca – dlatego za
nowy węzeł nadrzędny wybieramy nowo dodany węzeł.
Zamieniamy ze sobą miejscami węzeł nadrzędny z węzłem
dodanym – czyli 7 z 9.
Po zamianie węzła 7 z 9 warunek kopca jest spełniony, możemy
przejść do dodawania kolejnych elementów zbioru.
9
6
Dodajemy kolejny element ze zbioru. Sprawdzamy warunek kopca.
W tym przypadku dodany element 9 jest większy od elementu go
poprzedzającego – warunek kopca nie jest spełniony dlatego
wykonujemy kolejną operację.
7
Przykład 2 cd.
9
6
3
8
4
7
11
Dołączenie ostatniego elementu znów narusza warunek kopca.
Zamieniamy miejscami węzeł 8 z węzłem 11.
7 6 9 3 4 8 11
9
6
3
11
4
7
8
Po wymianie węzłów 8 i 11 warunek kopca został przywrócony,
ale tylko na tym poziomie. Widzimy, że węzeł 11 stał się
dzieckiem węzła 9 – na tym poziomie warunek kopca został
naruszony. Musimy dokonać zamiany miejsc węzła 9 i 11 aby
przywrócić warunek kopca.
11
6
3
9
4
7
8
Wymiana węzłów 9 i 11 to ostatni krok w tworzeniu danego
kopca. W całym drzewie spełniony jest warunek kopca – zadanie
zostało rozwiązane.
Specyfikacja algorytmu konstrukcji kopca
Dane wejściowe :
d [ ] - Zbiór zawierający elementy do wstawienia do kopca. Numeracja elementów rozpoczyna
się od 1
n - Ilość elementów w zbiorze, nN
Dane wyjściowe :
d [ ] - zbiór zawierający kopiec
Zmienne pomocnicze :
i - zmienna licznikowa pętli umieszczającej kolejne elementy zbioru w kopcu, i  N,
I  {2,3,...,n}
j,k - indeksy elementów leżących na ścieżce od wstawianego elementu do korzenia, j,k C
x - zmienna pomocnicza przechowująca tymczasowo element wstawiany do kopca
Lista kroków :
K01: Dla i = 2, ..., n: wykonuj kroki 2...5
K02: j := i; k := j div 2;
K03: x := d [ i ];
K04: Dopóki ( k > 0 ) and ( d [k] < x ) wykonuj :
d [ j ] := d [ k ];
j := k;
k := j div 2;
K05: d [ j ] := x;
K06: Koniec
START
Schemat blokowy
Przedstawiony algorytm ma klasę złożoności
O(n). Kopiec jest tworzony w tym samym zbiorze
wejściowym d[ ].
W pętli 1 kolejne elementy zbioru są wstawiane
do kopca. Rozpoczynamy od elementu drugiego,
ponieważ pierwszy i tak zostaje na swoim miejscu.
Inicjujemy także następujące zmienne: j – pozycja
wstawianego elementu (liścia), k – pozycja rodzica
(elementu nadrzędnego), x – zapamiętująca wstawiany
element.
Zadaniem pętli 2 jest znalezienie w kopcu miejsca
do wstawienia zapamiętanego elementu w zmiennej x.
Pętla jest wykonywana do momentu k=0 – osiągnięcia
korzenia lub znalezienia przodka większego od
zapamiętanego elementu – złamania warunku kopca.
W takim przypadku następuje zamiana miejsc
elementów tak aby warunek kopca został spełniony. Po
zakończeniu pętli w zmiennej j znajduje się numer
pozycji w zbiorze d[ ], na której należy umieścić
element w x.
Po zakończeniu pętli nr 1 w zbiorze zostaje
utworzona struktura kopca.
i:=2
i<=n
NIE
KONIEC
TAK
j:=i
k:=j div 2
x:=d[i]
1
k>0
2
NIE
TAK
d[k]<x
NIE
TAK
d[j]:=x
d[j]:=d[k]
j:=k
k:=j div 2
i:=i+1
Rozbiór kopca
Rozbiór kopca polega na :
• usunięciu korzenia kopca i wstawieniu w jego miejsce ostatniego elementu
kopca
• sprawdzeniu, czy warunek kopca jest spełniony, jeżeli nie, należy tak ustawić
elementy kopca, by ten warunek został spełniony
8
• rozbioru dokonujemy tak długo, aż kopiec będzie pusty
6
5
Zilustrujmy to na przykładzie rozbioru następującego kopca:
2
8
8
1
6
2
5
5
7
6
1
5
1
2
5
5
7
5
6
7
5
6
2
1
5
7
7
1
Rozbiór rozpoczynamy od usunięcia korzenia
i wstawiamy na jego miejsce ostatni element
kopca.
Krok ten powoduje zaburzenie
struktury kopca.
5
2
5
7
6
2
7
5
1
6
2
5
5
1
W kolejnych krokach sprawdzamy czy warunek kopca został spełniony na poszczególnych
poziomach struktury. Dokonujemy zamiany węzłów do momentu spełnienia warunku.
Rozbiór kopca cd.
78
7
1
6
2
5
5
6
1
5
2
5
1
6
2
Ponownie usuwamy korzeń i wstawiamy na
jego miejsce ostatni element kopca.
6
5
6
1
5
5
2
5
5
5
2
1
Struktura
kopca
została
naruszona.
Dokonujemy
wymiany węzłów między sobą
tak aby w drzewie spełniony
został warunek kopca.
678
6
5
2
1
5
5
1
2
1
5
2
Usuwamy kolejny korzeń i wstawiamy na
jego miejsce ostatni liść. Warunek kopca
zostaje naruszony.
5
5
5
1
2
5
5
2
1
5
Ponownie
wymieniamy
elementy kopca tak aby
warunek
kopca
został
spełniony.
Rozbiór kopca cd.
5678
5
2
1
5
2
5
Usuwamy korzeń i wstawiamy na jego
miejsce ostatni element kopca.
1
5
1
2
2
5
1
Sprawdzamy czy struktura kopca została
naruszona.
Dokonujemy
wymiany
węzłów między sobą tak aby w drzewie
spełniony został warunek kopca.
55678
5
2
1
1
2
2 55678
1
2
Usuwamy kolejny korzeń i wstawiamy na
jego miejsce ostatni element drzewa. Teraz
zostaje nam już tylko jedna wymiana.
2
1
1
1 2 55678
Wymieniliśmy ostatnie elementy
drzewa. Zaczynając od korzenia
dodajemy pozostałe elementy do
posortowanego zbioru.
Kopiec
został rozebrany.
Specyfikacja algorytmu rozbioru kopca
Dane wejściowe :
d [ ] - Zbiór zawierający poprawną strukturę kopca. Numeracja elementów rozpoczyna się od 1
n - Ilość elementów w zbiorze, nN
Dane wyjściowe :
d [ ] - zbiór zawierający pobrane z kopca ułożone w porządku rosnącym
Zmienne pomocnicze :
i - zmienna licznikowa pętli pobierającej kolejne elementy z kopca, i  N, i  {n, n-1,...,2}
j,k - indeksy elementów leżących na ścieżce w dół od korzenia, j,k N
m - indeks większego z dwóch elementów potomnych, m  N
Lista kroków:
K01: Dla i = n, n - 1, ..., 2: wykonuj K02...K08
K02: d[1] ↔ d[i]
K03: j ← 1; k ← 2
K04: Dopóki (k < i) wykonuj K05...K08
K05:
Jeżeli (k + 1 < i) ∧ (d[k + 1] > d[k]), to m ← k + 1
inaczej m ← k
K06:
Jeżeli d[m] ≤ d[j], to wyjdź z pętli K04 i kontynuuj następny obieg K01
K07:
d[j] ↔ d[m]
K08:
j ← m; k ← j + j
K09: Koniec
START
Schemat blokowy
Celem pętli nr 1 jest zamiana miejscami
kolejnych ostatnich kiści z korzeniem drzewa,
natomiast pętli 2 - przywrócenie strukturze
warunku kopca.
Pętla nr 1 przegląda elementy zbioru od
końca od n do 2. Element i-ty zostaje wymieniony
z korzeniem kopca. Po tej zamianie rozpoczynamy
w pętli 2 przeglądanie drzewa od korzenia w dół.
Zmienna j przechowuje indeksy przodka, zmienna
k przechowuje indeks lewego potomka. Koniec
pętli nr 2 następuje gdy węzeł j-ty nie ma już
potomka. Zmienna m wyznacza nam indeks
większego potomka z dwóch węzłów dzieci.
Następnie sprawdzamy czy zachowany jest
warunek kopca, jeśli tak to kończymy pętlę, jeśli
nie to zmieniamy miejscami węzeł nadrzędny j-ty
z jego największym potomkiem m-tym i jako nowy
węzeł nadrzędny przyjmujemy węzeł m-ty. W
zmiennej k wyznaczamy indeks lewego potomka i
kontynuujemy pętlę 2. Każde wyjście z pętli 2
zmniejsza zmienną i o 1, ponownie przenosimy się
do ostatniego liścia i kontynuujemy pętlę 1.
in
1
i>1
KONIEC
TAK
d[1]  d[i]
j 1
k 2
2
k<i
TAK
k+1<i
TAK
d[k+1]>d[k]
TAK
m k+1
d[m] ≤ d[j]
d[j]  d[m]
j m
k j+j
m k
TAK
i i – 1
Na podstawie przedstawionego algorytmu możemy ustalić klasę
złożoności obliczeniowej rozbioru kopca.
Przeanalizujmy schemat blokowy rozbioru kopca. Pętla zewnętrzna nr 1
wykona się n - 1 razy, a w każdym obiegu tej pętli pętla wewnętrzna wykona
się maksymalnie log2n razy. Dlatego, gdy dokonamy górnego oszacowania
otrzymamy
klasę
złożoności
obliczeniowej
rzędu
O(n log n) w przypadku pesymistycznym.
Przypadek optymistyczny wystąpi tylko wtedy, gdy zbiór d[ ]będzie
zbudowany z ciągu tych samych elementów. Dla tego przypadku klasa
złożoności obliczeniowej będzie wynosić O(n).
Sortowanie przez kopcowanie (heap sort)
W tej lekcji poznaliście już w jaki sposób tworzyć kopiec i jak dokonać jego
rozbioru.
Sortowanie przez kopcowanie (sortowanie kopcem) to nic innego jak
połączenie tych dwóch elementów. W zbiorze tworzymy kopiec, a następnie
dokonujemy jego rozbioru. W wyniku tego zbiór zostanie posortowany.
Sortowanie to jest dość szybkim algorytmem i nie pochlania zbyt wiele
zasobów pamięci. Jego asymptotyczna złożoność czasowa to O(n log n). W praktyce
algorytm ten jest nieco wolniejszy od sortowania szybkiego, ale ma lepszą
pesymistyczną złożoność czasową - O(n log n), podczas gdy dla quicksort jest to
O(n2) co jest nie do przyjęcia dla dużych zbiorów danych.
Sortowanie przez kopcowanie jest niestabilne, co może być czasami uznawane
za wadę. Algorytm sortuje w miejscu.
Sortowanie kopcem – specyfikacja problemu
Dane wejściowe
d[ ] - zbiór zawierający elementy do posortowania, które są numerowane począwszy
od 1. n - Ilość elementów w zbiorze, n  N
Dane wyjściowe
d[ ] - zbiór zawierający elementy posortowane rosnąco
Zmienne pomocnicze i funkcje
Twórz_kopiec - procedura budująca kopiec z elementów zbioru d[ ]. Kopiec powstaje
w zbiorze d[ ].
Rozbierz_kopiec - procedura dokonująca rozbioru kopca utworzonego w zbiorze d[ ].
Wynik rozbioru trafia z powrotem do zbioru d[ ]
START
Lista kroków
K01: Twórz_kopiec
Twórz_kopiec
K02: Rozbierz_kopiec
K03: Koniec
Rozbierz_kopiec
KONIEC
Bibliografia
•
•
•
•
•
•
•
•
Thomas H. Cormen, Leiserson C. E., Rivest R. L.: Wprowadzenie do
algorytmów, WNT 2001
Wirth N.: Algorytmy + struktury danych = program, WNT, Warszawa 2002
Sysło M.: Algorytmy, WSiP, Warszawa 1997
Wróblewski P.: Algorytmy, struktury danych i techniki programowania,
Wyd. Helion, 2003
www.pl.wikipedia.org
www.algorytm.org
www.encyklopedia.pwn.pl
www.edu.i-lo.tarnow.pl
Download