head[x]=NIL

advertisement
Strategia „dziel i zwyciężaj”
Wiele ważnych algorytmów ma strukturą rekurencyjną.
W celu rozwiązania rozwiązania problemu algorytm wywołuje sam
siebie przy rozwiązywaniu podobnych podproblemów.
W metodzie „dziel i zwyciężaj”:
(1) problem dzielony jest na kilka mniejszych podproblemów
podobnych do początkowego problemu.
(2) podproblemy rozwiązywane są rekurencyjnie
(3) rozwiązania wszystkich problemów są łączone w celu
utworzenia rozwiązania całego problemu.
1
Algorytm sortowania przez scalanie –
rekurencyjny algorytm sortujący.
Listę
A1,…,An
dzielimy na dwie listy o dwukrotnie mniejszych rozmiarach. Następnie
obie listy są sortowane osobno.
Aby zakończyć proces sortowania oryginalnej listy n-elementów, obie
listy zostają scalone przy pomocy specjalnego algorytmu.
Podstawa:
Indukcja:
Jeśli lista do posortowania jest pusta lub jednoelementowa,
zostaje zwrócona ta sama lista – jest ona już posortowana.
Jeżeli lista ma nie mniej niż 2 elementy to podziel listę na
dwie. Posortuj każdą z dwóch list i scal.
2
Rekurencyjne dzielenie i scalanie
Dzielenie
52461326
5246
2
4
6
26
13
46
52
5
1326
1
3
2
6
3
Rekurencyjne dzielenie i scalanie
Scalanie
12234566
1236
2456
5
2
4
6
26
13
46
25
1
3
2
6
Jaka jest procedura scalająca?
4
Procedura scalania
Oznaczmy przez A[0…n] i B[0…m] ciągi, które chcemy scalić do ciągu
C[0..m+n].
Procedura scalania jest następująca:
(1) Utwórz wskaźniki na początki ciągów A i B -> i=0, j=0
(2) Jeżeli A[i]<=B[j] wówczas wstaw A[i] do C i zwiększ i o jeden. W
przeciwnym przypadku wstaw B[j] do C i zwiększ j o jeden
(3) Powtarzaj krok 2 aż wszystkie wyrazy A i B trafią do C
A więc scalenie dwóch ciągów wymaga O(n+m) operacji porównań
elementów i wstawienia ich do tablicy wynikowej.
5
Złożoność czasowa
Można pokazać ze algorytm sortowania przez scalanie zachowuje się jak
O(n log n) (przypomnijmy, że algorytm sortowania przez wybieranie
zachowuje się jak O(n2).
Dla małych n algorytm sortowania przez wybieranie jest szybszy niż
sortowania przez scalanie.
6
Elementarne struktury danych
Standardowe typy proste – typy danych, które w większości
maszyn cyfrowych występują jako „możliwości wbudowane”.
Należą do nich: zbiór liczb całkowitych, zbiór wartości
logicznych i zbiór znaków drukarki.
integer, Boolean, char
Standardowo w komputerach można również skorzystać z liczb
ułamkowych (real) oraz z prostych operatorów (+,-,*,/).
Typ Boolean ma dwie wartości: true i false. Do operatorów
boolowskich zaliczamy:
koniunkcję-, alternatywę- i negację-.
7
Tablica
Tablica jest strukturą jednorodną – jest złożona ze składowych
tego samego typu zwanego typem podstawowym.
Tablica jest strukturą o dostępie swobodnym tzn. wszystkie
składowe mogą być wybrane w dowolnej kolejności i są
jednakowo dostępne.
W celu wybrania pojedynczej składowej nazwę tablicy uzupełnia
się tzw. indeksem wybierającym składową.
Indeks ten powinien być pewnego typu zwanego typem
indeksującym tablicy.
8
Przykłady
int alfa[20] - tablica zawierająca dane typu integer
char wiersz[100] - tablica zawierająca dane typu char
indeks liczbowy
Tablica asocjacyjna:
$dane_osobowe["imie"] = "Jan";
$dane_osobowe["nazwisko"] = "Kowalski";
$dane_osobowe["adres"] = "Polna 1";
indeks znakowy
9
Struktury danych
Zbiory są fundamentalnym pojęciem w matematyce, a także w
informatyce, ale…
…w informatyce interesujące są zbiory dynamiczne czyli zbiory,
które mogą się powiększać, zmniejszać lub w jakiś sposób
zmieniać w czasie.
Algorytmy działają na zbiorach danych.
Dynamiczny zbiór danych, na którym można wykonać operację
wstawiania elementu, usuwania elementu oraz sprawdzania, czy
dany element należy do zbioru nazywamy słownikiem.
10
Każdy element zbioru jest reprezentowany przez obiekt, którego
pola można odczytywać i modyfikować.
Wartości niektórych pól mogą być zmieniane przez operacje na
zbiorze; pola te mogą zawierać np. wskaźniki do innych elementów
zbioru.
W niektórych rodzajach zbiorów dynamicznych zakłada się, że
jedno z pól każdego obiektu wyróżnione jest jako jego klucz (ang.
key).
2 pola
9
11
-3
5
12
key next
Jeżeli klucze wszystkich elementów są różne to o zbiorze
dynamicznym możemy myśleć jak o zbiorze kluczy.
11
Operacje na zbiorach dynamicznych
Operacje na zbiorach dynamicznych można podzielić na dwie grupy:
zapytania – operacje pozwalające uzyskać pewne informacje na
temat zbioru.
Przykłady
Search(S,k) – zapytanie, które dla danego zbioru S oraz
wartości klucza k, daje w wyniku wskaźnik x do takiego elementu
w zbiorze, że key[x]=k lub NIL, jeżeli żaden taki element do
zbioru nie należy.
Minimum(S) – zapytanie dotyczące liniowo uporządkowanego
zbioru S, które daje w wyniku element S o najmniejszym kluczu.
12
Operacje na zbiorach dynamicznych cd.
operacje modyfikujące – operacje, które pozwalają zmienić zbiór
Przykłady
Insert(S,x) – operacja modyfikująca, która do danego zbioru
S dodaje element wskazywany przez x. Zakładamy, że wartości
wszystkich pól elementu wskazywanego przez x istotnych dla
realizacji zbioru zostały już zainicjowane.
Delete(S,x) – operacja modyfikująca, która z danego zbioru
S usuwa element wskazywany przez x.
13
Stos
Liniowa struktura danych. Dane dokładane są na wierzch stosu, również z
wierzchołka są ściągane (stosuje się też określenie LIFO (ang. Last In First
Out), oddające tę samą zasadę).
Przykład: stos książek, stos talerzy.
Operacje, jakie można wykonywać na stosie:
PUSH - czyli odłożenie obiektu na stos; rozmiar sosu zwiększa się o 1. Jeżeli
przekroczony zostaje maksymalny rozmiar stosu następuje jego
przepełnienie (ang. stack overflow).
POP
- ściągnięcie obiektu ze stosu; może doprowadzić do niedopełnienia
stosu (ang. stack underflow).
14
Implementacja stosu za pomocą tablicy
Stos S zawierający nie więcej niż n-elementów można zaimplementować w
tablicy S[1…n]. Z tablicą taką związany jest dodatkowy atrybut top[S], którego
wartość jest numerem ostatnio wstawionego elementu.
Stos składa się z elementów S[1],…,S[top[S]], gdzie S[1] jest elementem na
dnie stosu, a S[top[S]] jest elementem na wierzchołku stosu.
Jeżeli top[S]=0 wówczas stos jest pusty.
Do sprawdzenia czy stos S jest pusty używamy operacji Stack-Empty.
Stack-Empty(S)
if top[S]=0
then return True
else return False
15
Implementacja operacji na stosie:
PUSH
<- top[S]
Push(S,x)
top[S]<-top[S]+1
S[top[S]]=x
POP
<- top[S]
Pop(S)
if Stack-Empty(S)
then error „niedomiar”
else
top[S]<-top[S]-1
return S[top[S]+1]
16
Przykład
S
1
2
3
4
15
6
2
9
5
6
7
Push(S,17)
top[S]=4
Push(S,3)
S
1
2
3
4
5
6
15
6
2
9
17
3
7
top[S]=6
Pop(S)
S
1
2
3
4
5
6
15
6
2
9
17
3
7
top[S]=5
17
Kolejka
Liniowa struktura danych. Kiedy wstawiamy nowy element do kolejki, zostaje
on umieszczony na końcu kolejki (w ogonie). Element może zostać usunięty z
kolejki tylko wtedy gdy znajduje się na początku kolejki (w głowie). W
przypadku kolejki stosuje się też określenie LIFO (ang. Last In First Out).
Przykład: kolejka ludzi w sklepie.
Operacje, jakie można wykonywać na kolejce:
ENQUEUE - czyli wstawienie elementu do kolejki.
DEQUEUE - czyli usunięcie elementu z kolejki.
18
Implementacja kolejki za pomocą tablicy
Kolejkę Q o co najwyżej n-1 elementach można zaimplementować za pomocą
tablicy Q[n…1]. Atrybut head[Q] takiej kolejki wskazuje na jej głowę, tj. na
początek, natomiast atrybut tail[Q] wyznacza następną wolną pozycję, na którą
możemy wstawić do kolejki nowy element.
Elementy kolejki znajdują się na pozycjach
head[Q], head[Q]+1, …, tail[Q]-1
Załóżmy, że tablica Q jest „cykliczna”, tzn, pozycja o numerze 1 jest
bezpośrednim następnikiem pozycji o numerze n.
Jeżeli head[Q]=tail[Q] to kolejka jest pusta.
Początkowo head[Q]=tail[Q]=1 to kolejka jest pusta.
Jeżeli head[Q]=tail[Q]+1 to kolejka jest pełna.
Jeżeli kolejka jest pusta wówczas próba usunięcia elementu z kolejki jest
sygnalizowana jako błąd niedomiaru. Próba wstawienia nowego elementu do
pełnej kolejki sygnalizowana jest jako błąd przepełnienia.
19
Implementacja operacji na kolejce przy pomocy tablicy:
WSTAWIENIE
tail[Q]-1
ENQ(Q,x)
Q[tail[Q]]<-x
if tail[Q]=length[Q]
then tail[Q]<-1
else tail[Q]<-tail[Q]+1
USUNIĘCIE
head[Q]
DEQ(Q)
x<-Q[head[Q]]
if head[Q]=length[Q]
then head[Q]<-1
else head[Q]<-head[Q]+1
return x
20
UWAGA: Obsługa błędów przepełnienia i niedomiaru pominięta – praca domowa!
Przykład
Q
1
2
3
4
5
6
7
6
9
8
4
head[Q]=4
8
9
10
Enq(Q,17)
tail[Q]=8
Enq(Q,3)
Enq(Q,5)
Q
1
2
tail[Q]=1
3
4
5
6
7
8
9
10
6
9
8
4
17
3
5
head[Q]=4
Deq(Q)
Q
1
2
tail[Q]=1
3
4
5
6
7
8
9
10
6
9
8
4
17
3
5
head[Q]=5
21
Lista
Lista – struktura danych w których elementy są ułożone w liniowym porządku.
Porządek na liście określają wskaźniki związane z każdym elementem listy.
Lista jednokierunkowa
head[L]->
11
9
-3
5
prev
key
12
next
Lista dwukierunkowa
head[L]->
9
4
-2
1
22
Lista
next[x] – następnik elementu x. Jeżeli next[x]=NIL to x nie ma
następnika, jest więc ostanim elementem listy (tzw. ogon).
prev[x] – poprzednik elementu x. Jeżeli prev[x]=NIL to x nie ma
poprzednika, jest więc pierwszym elementem listy (tzw. głowa).
head[L] – pierwszy element listy L. Jeżeli head[x]=NIL to lista
jest pusta.
23
Wstawianie do listy z dowiązaniami
List-Insert(L,x)
next[x]<-head[L]
if head[L]!=NIL
then prev[head[L]]<-x
head[L]<-x
prev[x]<-NIL
Procedura List-Insert(L,x) przyłącza element x (dla którego
pole key zostało wcześniej zainicjowane) na początek listy.
24
Usuwanie z listy z dowiązaniami
List-Delete(L,x)
if prev[x]!=NIL
then next[prev[x]]<-next[x]
else head[L]<-next[x]
if next[x]!=NIL
then prev[next[x]]<-prev[x]
Procedura List-Delete(L,x) usuwa element x z listy L.
25
Wyszukiwanie na listach z dowiązaniami
List-Search(L,k)
x<-head[L]
while x!=NIL and key[x]!=k
do x<-next[x]
return x
Procedura List-Search(L,k) wyznacza pierwszy element o
kluczu k na liście L.
26
Przykład
head[L]
9
head[L]
25
head[L]
25
4
-2
1
key[x]=25
List-Insert(L,x)
9
4
-2
1
key[x]=4
List-Delete(L,x)
9
-2
1
27
Rekurencyjna definicja listy
Listę można zdefiniować w następujący rekursyjny sposób:
Lista o typie podstawowym T jest albo:
(1) pustą listą, albo
(2) konkatenacją (połączeniem) elementu typu T i listy o typie
podstawowym T
W podobny sposób można zdefiniować strukturą drzewiastą (drzewo).
Jest ona albo:
(1) strukturą pustą, albo
(2) węzłem typu T ze skończoną liczbą dowiązań rozłącznych
struktur drzewiastych o typie podstawowym T, nazywanych
poddrzewami.
28
Drzewa
Drzewa są zbiorami punktów, zwanych węzłami lub wierzchołkami, oraz
połączeń, zwanych krawędziami.
(A) Krawędź łączy dwa różne węzły.
n1
(B) W każdym drzewie wyróżniamy
jeden węzeł zwany korzeniem – n1
(C) Każdy węzeł n nie będący
korzeniem jest połączony krawędzią z
innym węzłem zwanym rodzicem.
Węzeł n nazywamy dzieckiem.
n2
n5
n3
n4
n7
n6
(D) jeżeli rozpoczniemy analizę od węzła
n nie będącego korzeniem i przejdziemy
do rodzica tego węzła, osiągniemy w
końcu korzeń. Mówimy, że drzewo jest
spójne.
29
Drzewa
Reprezentacja - graf
korzeń
A
B
krawędź
C
rodzic
F
E
D
dziecko
G
H
I
J
K
L
30
Drzewa
(E) Relację rodzic-dziecko w naturalny
sposób można rozszerzyć do relacji przodekpotomek.
w1
(F) Liściem nazywamy węzeł drzewa
który nie ma potomków.
(G) Węzeł nazywamy wewnętrznym
jeżeli ma jednego lub większą liczbę
potomków.
w2
(D) W dowolnym drzewie T, dowolny
węzeł n wraz z jego potomkami
nazywamy poddrzewem.
w5
w3
w4
w7
w6
31
Drzewa
Reprezentacja - graf
A
przodek L
B
C
węzeł
wewnętrzny
F
E
D
potomek L
G
H
liść
I
J
K
L
liść
32
Drzewa
(E) W drzewie istnieje dokładnie jedna ścieżka pomiędzy węzłem a
korzeniem. Przez ścieżkę rozumiemy ciąg krawędzi.
(F) Liczba krawędzi w ścieżce jest nazywana długością (lub
głębokością) – liczba o jeden większa określa poziom węzła.
(G) Z kolei wysokość drzewa definiujemy jako długość najdłuższej
ścieżki wychodzącej z korzenia.
(H) Głębokość węzła to długość ścieżki od korzenia do tego węzła.
33
Drzewa
Reprezentacja struktury drzewiastej - graf
ścieżka o
długości 3
A
B
C
ścieżka o
długości 2
G
F
E
D
H
I
Drzewo o wysokości 3
J
K
L
34
Inne reprezentacje
zbiory zagnieżdżone
G
I
J
H E
D
B
nawiasy zagnieżdżone
K
L
F
C
A
(A(B(D(G),E(I,J,H)),C(F(K,L))))
wcięcia
A
B
D
G
E
I
J
H
C
F
K
L
35
Liczbę bezpośrednich potomków węzła wewnętrznego nazywamy
jego stopniem.
Maksymalny stopień węzłów jest stopniem drzewa.
Drzewo nazywamy uporządkowanym jeżeli gałęzie każdego węzła są
uporządkowane.
Drzewa uporządkowane o stopniu 2
nazywamy drzewami binarnymi.
w1
w4
w2
w6
w4
w8
w6
w5
w9
w7
w10
36
Drzewa binarne
Rekurencyjna definicja drzewa binarnego
R
A
B
Podstawa: Drzewo puste jest drzewem binarnym.
Krok: Jeśli R jest węzłem oraz A, B są drzewami binarnymi to
istnieje drzewo binarne z korzeniem R, lewym poddrzewem A i
prawym poddrzewem B.
Korzeń drzewa A jest lewym dzieckiem węzła R, chyba że A jest
drzewem pustym. Podobnie korzeń drzewa B jest prawym
dzieckiem węzła R, chyba że B jest drzewem pustym.
37
Drzewo binarne
W każdym węźle drzewa binarnego T znajduje się wskaźnik do ojca
oraz lewego i prawego syna, odpowiednio w polach p, left, right.
p
left
key
left
Jeżeli p[x]=NIL to węzeł x jest korzeniem drzewa.
Jeżeli left[x]=NIL (right[x]=NIL) to węzeł x nie ma lewego (prawego)
syna.
Atrybut root[T] zawiera wskaźnik do korzenia. Jeżeli root[T]=NIL to
znaczy, że drzewo jest puste.
38
Drzewa binarne
root[T]
* Pola key nie zostały uwzględnione.
39
Rekurencja w drzewach
Często zdarza się, że musimy wykonać pewną operacje P na każdym z
elementów drzewa.
P stanowi wówczas parametr ogólniejszego zadania – odwiedzenia
wszystkich węzłów – nazywanego przeglądaniem drzewa.
Przeglądanie odbywa się zgodnie z pewnym porządkiem.
Istnieją trzy podstawowe uporządkowania:
(1) Preorder (wzdłużny): R, A, B
R
(2) Inorder (poprzeczny): A, R, B
(3) Postorder (wsteczny): A, B, R
A
B
40
Przykład
*
_
+
a
d
/
b
C
*
e
f
(1) Preorder (R,A,B):
*+a/bc–d*ef
(2) Inorder (A,R,B):
a+b/c*d–e*f
(3) Postorder (A,B,R):
abc/+def*–*
41
Drzewa przeszukiwań binarnych
Drzewa poszukiwań binarnych (ang. binary search trees - BST) to
drzewa binarne posiadające następującą własność:
Niech x będzie węzłem drzewa BST. Jeżeli y jest węzłem znajdującym
się w lewym poddrzewie węzła x, to
key[y]key[x]
Jeżeli y jest węzłem znajdującym się w prawym poddrzewie węzła x,
to
key[x]key[y]
2
5
3
2
3
7
7
5
5
8
8
5
42
Wyszukiwanie elementu
Podstawa Jeśli drzewo T jest puste, to na pewno nie zawiera elementu x. Jeśli
T nie jest puste i szukana wartość x znajduje się w korzeniu, drzewo zawiera x.
Krok Jeśli T nie jest puste, ale nie zawiera szukanego elementu x w korzeniu,
niech y będzie elementem w korzeniu drzewa T.
Jeżeli x<y, szukamy dalej tego elementu tylko w lewym poddrzewie korzenia;
Jeżeli x>y, szukamy wartości x tylko w prawym poddrzewie korzenia y.
Przykład
5
Szukamy liczby 8. Zaczynamy od
korzenia czyli liczby 5.
Ponieważ 5<8 zatem przechodzimy
do prawego poddrzewa.
3
2
7
5
8
43
Wyszukiwanie elementu
Tree-Search(x,k)
if
x=NIL
lub
x – wskaźnik do korzenia drzewa
k=key[x]
k - klucz
then return x
if
k<key[x]
then return Tree-Search(left[x], k)
then return Tree-Search(right[x], k)
Wstawianie elementu
Usuwanie elementu
praca domowa
44
Download