Dynamiczne struktury danych Listy, Kolejki, Stosy
Lista jednokierunkowa
Lista dwukierunkowa
Lista cykliczna jednokierunkowa
Lista cykliczna dwukierunkowa
z równo rozmieszczonymi węzłami Lista z przeskokami z równo rozmieszczonymi węzłami z nierówno rozmieszczonymi węzłami
Listy samoorganizujące się Najważniejsze sposoby organizowania list: Metoda przesuwania na początek Metoda transpozycji Metoda zliczania Metoda porządkowania.
Operacja na listach Zakładamy, że operujemy na listach nieposortowanych i dwukierunkowych.
Przeszukiwanie listy void lista_szukaj(lista,k){ x = lista.glowa while (x!=NIL)&&(x.klucz!=k) x=x.nastepny return x; }
Wstawianie elementów void lista_wstaw(lista, x){ x.nastepny = L.glowa if L.glowa!=NIL L.glowa.poprzedni =x L.glowa=x x.poprzedni = NIL }
Usuwanie elementów void lista_usun(lista, x){ jeśli x.poprzedni !=NIL x.poprzedni.nastepny=x.naste pny else lista.glowa=x.nastepny if x.nastepny!=NIL x.nastepny.poprzedni=x.poprz edni }
Wartownicy Wartownik to element, który wstawia się do listy by uprościć warunki brzegowe. Dla omawianej listy przyjmijmy że będzie nim element Lista.nil, wówczas każde występienie stałej NIL w naszych procedurach zmieniamy na wskaźnik do wartownika Lista.nil. Nasza lista dwukierunkowa staje się listą cykliczną, w której wartownik Lista.nil znajduje się między głową a ogonem czyli atrybut Lista.nil.nastepny wskazuje na glowe listy, a Lista.nil.poprzedni wskazuje na ogon listy. Zarówno atrybut nastepny ogona i atarybut poprzedni glowy listy wskazuja na lista.nil.
Wartownicy - modyfikacja void lista_szukaj_w(lista,k){ x = lista.nil.nastepny while (x!=lista.nil)&&(x.klucz!=k) x=x.nastepny return x; } void lista_wstaw_w(lista, x){ x.nastepny = lista.nil.nastepny lista.nil.nastepny.poprzedni=x lista.nil.nastepny=x x.poprzedni=lista.nil void lista_usun_w(lista, x){ x.poprzedni.nastepny=x.nastepny x.nastepny.poprzedni=x.poprzedni
Lista – jako tablica Wielowymiarowa tablica Jednowymiarowa tablica 1 2 3 4 5 6 7 następny / klucz 8 poprzedni 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 / klucz następny poprzed.
Kolejka Struktura, w której dostęp do danych możliwy jest z dwóch końców, z tym, że z jednego końca dane są usuwane a do drugiego dodawane. Kolejka to struktura FIFO (First in/ First out) – pierwszy wchodzi, pierwszy wychodzi.
Operacje na kolejkach
Operacje - pseudokod Dokładanie elementu void enqueue(int kolejka[], int liczba){ kolejka[ogon]=liczba; if (ogon==n ) // n – rozmiar tablicy kolejka; ogon=1; else ogon++; } Usuwanie elementu kolejki int dequeue (int kolejka[]){ liczba=kolejka[glowa]; if glowa==n glowa=1; else glowa++; return liczba;
Stos Liniowa struktura danych, do której dostęp możliwy jest z jednego końca. Nazywamy czasem strukturą LIFO (Last in/first out) – ostatni wchodzi a pierwszy wychodzi.
Operacje na stosie
Operacje - pseudokod Sprawdzanie czy stos jest pusty int stospusty(int stos[]){ if stos[wierzcholek]==0 return 1; else return 0; } Dołóż na stos void push(int stos[], int liczba){ stos[wierzcholek]++; stos[stos[wierzcholek]]=liczba; }
Operacje - pseudokod Zdejmij ze stosu int pop(int stos[]){ if stospusty(stos) { cout<<”pusty stos”; return -1; }else{ stos[wierzcholek]--; return Stos[Stos[wierzcholek]+1]; }
Drzewa ukorzenione Struktura, składająca się z węzłów, gdzie każdy węzeł zawiera klucz i wskaźnik do innych węzłów
Drzewo binarne Każdy węzeł x ma trzy atrybuty: p – górne pole, left – lewe dolne pole i right – prawe dolne pole. Atrybut klucz nie został uwzględniony na tym rysunku.
Drzewa o dowolnym stopniu rozgałęzień – sposoby prezentacji Liczba synów każdego węzła jest nie większa niż pewna stała. Podejście „na lewo syn, na prawo brat”. Każdy węzeł drzewa ma atrybut p wskazujący na ojca (T. root wskazuje na korzeń drzewa T), oraz dwa wskaźniki: x.left-child – wskazuje na najbardziej lewego syna węzła x. x.right-sibling wskazuje na najbliższego, znajdującego się na prawo brata węzła x.
Reprezentacja „na lewo syn, na prawo brat”
Drzewa wyszukiwań binarnych BST – binary search tree. Struktura - drzewem binarnym. Każdy węzeł zawiera atrybuty: key i ewentualnie left, right, oraz p, które wskazują odpowiednio na lewego, prawego syna oraz ojca. Klucze spełniają własnośćniech x będzie węzłem drzewa BST i ma dwóch potomków y – lewy syn i z – prawy syn, wtedy zachodzi własność, y.key≤x.key oraz z.key≥x.key.
Drzewa wyszukiwań binarnych
Operacje na drzewach wyszukiwań binarnych
BST – wypisanie elementów Metoda inorder- klucz korzenia wypisuje się pomiędzy wartościami z jego lewego poddrzewa oraz prawego podrzewa. metoda preorder - klucz korzenia wypisanyj jest przed wypisaniem wartości znajdujących się w obu poddrzewach. metoda postorder - klucz korzenia po wypisaniu wartości znajdującech się w poddrzewach.
BST - Inorder INORDER-TREE-WALK(x) if x!= NIL INORDER-TREE-WALK(x.left) wypisz x.key INORDER-TREE-WALK(x.right) Twierdzenie Jeśli x jest korzeniem poddrzewa o n węzłach, to wykonanie INORDER-TREE- WALK(x) odbywa się w czasie (n).
BST – wyszukiwanie - rekurencyjnie TREE-SEARCH(x,k) if x==NIL lub k==x.key return x if k<x.key return TREE- SEARCH(x.left,k) else return TREE- SEARCH(x.right, k)
BST – wyszukiwanie ITERATIVE-TREE-SEARCH(x,k) while x!=NIL i k!=x.key if k<x.key x=x.left else x=x.right return x
BST – wyszukiwanie - przykład Szukamy węzła o wartości klucza 9. TREE-SEARCH(15,9) 9<15 TREE-SEARCH(6,9) 9>6 TREE-SEARCH(9,9) 9=9 return 9
BST – Minimum, Maksimum TREE-MINIMUM(x) while x.left!=NIL x=x.left return x TREE-MAXIMUM(x) while x.right!=NIL x=x.right Koszt = O(h), gdzie h to wysokość drzewa.
BST – Następnik, Poprzednik TREE-SUCCESSOR(x) if x.right != NIL return TREE- MINIMUM(x.right) y=x.p while y!=NIL i x==y.right x=y y=y.p return y
BST - wstawianie TREE-INSERT(T,z) y=NIL, x=T.root while x!=NIL y=x if z.key<x.key x=x.left else x=x.right x.p=y if y==NIL T.root =z Else if z.key<y.key y.left=z else y.right=z
BST – wstawianie - przykład
BST - Usuwanie Jeśli z nie ma lewego syna to zastępujemy z przez jego prawego syna (być może równego NIL). Jeśli prawy syn jest równy NIL to z jest liściem a jeśli jest różny od NIL to z ma tylko jednego prawego syna. Jeśli z ma tylko jednego lewego syna, to zastępujemy z przez jego lewego syna. W przeciwnym razie z ma zarówno lewego, jak i prawego syna. Znajdujemy węzeł y będący następnikiem z w prawym poddrzewie z. Węzeł y nie ma lewego syna. Chcemy wysłuskać y z jego aktualnego położenia i zastąpić nim węzeł z w drzewie. Jeśli y jest prawym synem z, to zastępujemy z przez y, pozostawiając prawego syna y bez zmian. W przeciwnym razie y znajduje się w prawym poddrzewie z, ale nie jest prawym synem z. W tym przypadku najpierw zastępujmy y przez jego prawego syna, a potem zastępujemy z przez y.
BST – Usuwanie, przypadek 1 i 2
BST – usuwanie, przypadek 3
BST – przesuwanie poddrzew – procedura TRANPLANT TRANSPLANT (T,u,v) If u.p==NIL T.root=v Else if u==u.p.left u.p.left=v else u.p.right =v if v!=NIL v.p = u.p
BST – Usuwanie węzła TREE-DELETE (T,z) If z.left==NIL TRANSPLANT(T, z, z.right) Else if z.right ==NIL TRANSPLANT(T,z,z.left) Else y=TREE-MINIMUM(z.right) If y.p!=z TRANSPLANT(T,y,y.right) y.right=z.right y.right.p=y TRANPLANT(T,z,y) y.left=z.left y.left.p=y
Bibliografia Drozdek, C++ Algorytmy i Struktury danych, Helion, Gliwice 2004 Cormen Thomas; Leiserson Charles; Rivest Ronald; Stein Clifford, „Wprowadzenie do Algorytmów”, Wydawnictwo Naukowe PWN, Warszawa 2012