360 likes | 625 Views
Dynamiczne struktury danych. AiSD_W4. dr inż. Kustra Piotr. Opracowanie: Anna Adrian oraz Kustra Piotr. Tablice dynamiczne. program DynamicVec; var t1 : array of integer; t2 : array [1..10] of integer; nElem: integer; BEGIN read(nElem); write('Ile elementów? ', nElem );
E N D
Dynamiczne struktury danych • AiSD_W4 dr inż. Kustra Piotr • Opracowanie: Anna Adrian oraz Kustra Piotr
Tablice dynamiczne • program DynamicVec; • var • t1 : array of integer; • t2 : array [1..10] of integer; • nElem: integer; • BEGIN • read(nElem); • write('Ile elementów? ', nElem); • setlength(t1,nElem); • for i:=1 to nElemdo • begin • t1[i]:=0; • end; • end. • t2 • t1 • … • nElem
Macierz dynamiczna • program DynamicMatrix; • var • sizeX, sizeY, i, j:integer; • m:array of array of integer; • begin • size:=15; • SetLength(m, sizeX, sizeY); • for i:=0 to sizeX-1 do • begin • for j:=0 to sizeY-1 do • begin • m[i,j]:=1; • end; • end; • readln(); • end. • sizeY • sizeX
Zbiory dynamiczne • Zbiory • – w matematyce pojęcie fundamentalne, niezmienne; • - w informatyce zbiory mogą być zmieniane w wyniku działania algorytmów (powiększać, zmniejszać, albo zmieniać w czasie) – stąd ich nazwa zbiory dynamiczne. • Zbiór dynamiczny do którego można dodawać (wstawiać) elementy, usuwać elementy ze zbioru i sprawdzać czy element należy do zbioru nazywane są słownikami. • Metoda realizacji zbiorów dynamicznych zależy od operacji, które na konkretnym zbiorze mogą być wykonane.
Elementy zbioru dynamicznego • Każdy element zbioru dynamicznego jest reprezentowany przez obiekt, którego pola można odczytać lub modyfikować, jeśli mamy wskaźnik tego obiektu. • W pewnych rodzajach zbiorów dynamicznych wyróżnione jest jedno z pól każdego elementu i nazywane kluczem (key) obiektu. • Jeśli każdy element zbioru dynamicznego ma własny unikalny (niepowtarzalny) klucz, wtedy zwykle taki zbiór traktujemy jako zbiór kluczy. • W niektórych implementacjach zbiorów dynamicznych zakłada się, że zbiór kluczy jest liniowo uporządkowany • Obiekt może zawierać też inne dane (dodatkowe dane) zawarte w innych polach, jednak one nie wpływają na realizację zbioru • Istnieje możliwość występowania w obiekcie pól zawierających wartości zmieniające się przez operacje na zbiorze (np. wskaźniki do innych obiektów w zbiorze)
Operacje na zbiorach dynamicznych • Wyróżnia się dwie grupy operacji na zbiorach dynamicznych, • są to: • Zapytania– pozwalające uzyskać pewne informacje na temat zbioru: SEARCH, MINIMUM, MAXIMUM, SUCCESSOR(następca), PREDECESSOR(poprzednik) • Operacje modyfikujące– umożliwiające wprowadzanie zmian w zbiorze dynamicznym : INSERT i DELETE. • Wymienione operacje uznawane są jako typowe. • W konkretnych implementacjach zbiorów dynamicznych zwykle znajdują zastosowanie tylko niektóre z nich.
Operacje na zbiorach dynamicznychZapytania • SEARCH(S,k) - dla danego zbioru S i klucza k zwraca wskaźnik x do takiego obiektu w zbiorze S, dla którego key[x]=k albo NIL- gdy w zbiorze S nie znaleziono żadnego obiektu spełniającego podany warunek. • MINIMUM(S) – zwraca w wyniku element zbioru S o najmniej -szym kluczu; dotyczy zbiorów liniowo uporządkowanych. • MAXIMUM(S) wskazuje element zbioru S o największym kluczu; dotyczy zbiorów liniowo uporządkowanych.
Operacje na zbiorach dynamicznychZapytania • SUCCESSOR (S,x) - dla danego elementu x o kluczu należącym do uporządkowanego zbioru S daje w wyniku następnik elementu x w S, tj najmniejszy element ze zbioru S który jest większy od x. • Jeśli x jest największym elementem w zbiorze S wynikiem jest stała NIL • PREDECESSOR (S,x) dla danego elementu x o kluczu należącym do uporządkowanego zbioru S daje w wyniku poprzednik elementu x w S, tj największy element ze zbioru S który jest mniejszy od x. • Jeśli x jest najmniejszym elementem w zbiorze S wynikiem jest stała NIL
Operacje na zbiorach dynamicznychOperacje modyfikujące • INSERT(S,x) – dodaje do zbioru S wskazany element x. • Przy tej operacji zakłada się zwykle, że wcześniej zostały zainicjowane wartości wszystkich pól obiektu wskazanego przez x istotne dla realizacji zbioru S • DELETE(S,x)- dla danego wskaźnika x do obiektu zawartego • w zbiorze S usuwa ten element ze zbioru S. • Warto zauważyć, że argumentem tej operacji jest wskaźnik do elementu x a nie wartość jego klucza. • Czas wymagany do realizacji operacji na zbiorze wyrażany jest zwykle jako funkcja rozmiaru zbioru.
Elementarne dynamiczne struktury danych • Zbiory dynamiczne mogą być realizowane przez różne struktury danych. • W dalszym ciągu wykładu przedstawiono podstawy realizacji elementarnych struktur danych, takich jak: • Stosy • Kolejki • Listy • Drzewa
Stosy i kolejki • Stosy i kolejki są realizacją zbiorów dynamicznych w których element usuwany jest określony jednoznacznie: • stos: strategia LIFO (last in first out) –najpóźniej dodany usuwany jest jako pierwszy • kolejka: strategia FIFO (first in first out) _ najwcześniej usuwany jest element najstarszy • Stosy i kolejki mogą być efektywnie implementowane na wiele sposobów
Stos • Stos zawierający nie więcej niż n elementów można zaimplementować w n elementowej tablicy S[1..n] • Atrybut top [S] oznacza numer ostatnio wstawionego elementu do stosu. • Stos składa się z elementów: S[1..top[S]], gdzie • S[1] jest elementem na dnie stosu • S[top[S]] jest elementem na wierzchu stosu
Podstawowe operacje na stosie • Sprawdzam czy stos jest pusty: • Stack-Empty(S) • if top (S) =0 • then return TRUE • else return FALSE • Jeśli top[S] =0 to stos jest pusty. • Na próbę zdjęcia elementu ze stosu pustego powinien pojawić się komunikat o wystąpieniu błędu niedomiaru. • Błąd przepełnienia występuje wtedy gdy top[S] > n
Podstawowe operacje na stosie Insert – PUSH • PUSH (S,x) • top [S] top [S] +1 • S[ top [S]] x • --------------------------------------------------------------------------- • PUSH (S,17) • PUSH (S,3)
Podstawowe operacje na stosie Delete - POP • POP (S) • If STACK-EMPTY (S) • then error „niedomiar” • else top [S] top [S] -1 • return S[ top [S] +1] • ---------------------------------------------------------------------------
Kolejki • Kolejkę zawierającą nie więcej niż n elementów można zaimplementować w n elementowej tablicy Q[1..n] • Atrybut head [Q] wskazuje głowę (początek) kolejki • Atrybut tail[Q] wyznacza następną wolną pozycję na którą można wstawić do kolejki nowy element. • Jeśli head [Q] = tail[Q] to kolejka jest pusta. • Początkowo head [Q] = tail[Q] =1 • Jeśli kolejka jest pusta i próbujemy usunąć z niej element występuje wtedy błąd niedomiaru • Jeśli head [Q] = tail[Q] +1, to kolejka jest pełna i przy próbie wstawienia kolejnego elementu występuje błąd przepełnienia
Kolejki • Kolejka zawiera 5 elementów na pozycjach Q [7..11] • Elementy kolejki zajmują pozycje • head [Q], head [Q]+1,……………, tail[Q]-1 • Zakładamy, że Tablica Q jest cykliczna, tzn pozycja 1 jest bezpośrednim następnikiem pozycji n .
Podstawowe operacje na kolejkach ENQUEUE (Q,x) • ENQUEUE (Q,x) • Q [tail [Q]] x • if tail [Q]=length [Q] • then tail [Q] 1 • else tail [Q] tail [Q] +1 • ------------------------------------------------------------------------------------------------ • ENQUEUE (Q,17); ENQUEUE (Q,3); ENQUEUE (Q,5)
Podstawowe operacje na kolejkach DEQUEUE (Q) • DEQUEUE (Q,x) • x Q [head[Q]] • if head [Q]=length [Q] • then head[Q] 1 • else head[Q] head [Q] +1 • return x • ------------------------------------------------------------------------------------------------ • DEQUEUE (Q);
Kolejka dwustronna • W stosach wstawianie i usuwanie elementów jest realizowane tylko na jednym końcu, na górze stosu. • W kolejce dodajemy elementy na jednym końcu a usuwamy z drugiego końca. • Kolejka dwustronna (dwukierunkowa) jest strukturą danych pozwalającą na wstawianie i usuwanie elementów na obu końcach kolejki. • Napisać procedury służące do wstawiania elementów na obu końcach i do usuwania z obu końców kolejki zaimplementowanej w tablicy
Listy • Lista z dowiązaniami jest prostą i elastyczną strukturą danych, służącą do reprezentowania zbiorów dynamicznych, umożliwiającą wykonanie wszystkich typowych operacji na zbiorach. • Elementy listy ułożone są w porządku liniowym. • Porządek określają wskaźniki związane z każdym elementem listy, a nie jak w tablicach, gdzie porządek jest wyznaczony przez indeksy.
Atrybuty listy • Każdy element listy dwukierunkowej (doubly linked list) jest rekordem składającym się z trzech pól : • key[x] –zawierającego klucz elementu x, • prev[x]- wskazującego na poprzednika elementu x, Jeśli prev[x]=NIL to element x nie ma poprzednika, x jest pierwszym elementem listy, mówimy, że x jest wtedy głową (head) listy. • Jeśli head[L] = NIL to lista jest pusta • next[x] – wskazującego na następnika x na liście • Jeśli next [x]=NIL to element x nie ma następnika , czyli x jest ostatnim elementem listy, nazywanym ogonem listy
Rodzaje list • Lista jest jednokierunkowa (singly linked list) gdy pomijany jest wskaźnik prev • Lista jest posortowana gdy kolejność elementów na liście jest zgodna z porządkiem na ich kluczach; • - element o najmniejszym kluczu znajduje się w głowie, - element o największym kluczu w ogonie listy. • W liście nieposortowanej kolejność elementów jest dowolna. • W liście cyklicznej, elementy tworzą pierścień. • Pole prev elementu w głowie wskazuje na ogon, a pole next w ogonie na głowę listy cyklicznej.
Operacje na listach - wyszukiwanie • Założenie: listy są dwukierunkowe, nieposortowane. • Procedura LIST-SEARCH (L,k) wyznacza pierwszy element o kluczu k na liście L za pomocą prostego sortowania. Czas jej działania wynosi (n) • LIST-SEARCH (L,k) • x head[L] • while x NIL i key[x] k • do x next[x] • return x • Po wywołaniu procedury LIST-SEARCH (L,4) na liście otrzymamy wskaźnik do jej trzeciego elementu, a po wywołaniu LIST-SEARCH (L,7) otrzymamy NIL
Operacje na listach – wstawianie nowych elementów • Procedura LIST-INSERT(L,x) przyłącza element x, którego pole key zostało wcześniej zainicjowane, na początek listy. • Czas działania procedury na liście o n elementach wynosi O(1) • LIST-INSERT(L,x) • next[x] head[L] • if head[L] NIL • then prev[head[L] ] x • head[L] x • prev[x] NIL • ------------------------------------------------------------------------------------------------------------------ • Po wykonaniu procedury LIST-INSERT(L,x), gdzie key[x]=25 w głowie listy znajdzie się nowy rekord z kluczem 25 pole next zawiera wskaźnik do poprzedniej głowy z kluczem 9
Operacje na listach –usuwanie elementów z list z dowiązaniami • Wywołanie procedury LIST-DELETE (L,x) powoduje usunięcie elementu x z listy L. • Wycinanie elementu z listy polega na modyfikacji odpowiednich wskaźników. • Czas działania procedury na liście o n elementach wynosi O(1) ale pesymistyczny czas usuwania elementu o zadanym kluczu wynosi (n) • ponieważ • Najpierw musi zostać wywołana procedura LIST-SEARCH, aby wyznaczyć wskaźnik elementu x
Operacje na listach –usuwanie elementów z list 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] • ------------------------------------------------------------------------------ • Wynik wykonania operacji LIST-DELETE (L,x), gdzie x wskazuje na element o kluczu 4 z poprzedniej listy
Tablicowe reprezentacje struktur wskaźnikowych • Reprezentacja wielotablicowa • Dana jest lista: • Przedstawmy reprezentację tej listy za pomocą trzech tablic: • Tablica key zawiera wartości kluczy znajdujących się na liście • Tablice next i prev zawierają wskaźniki • Wartości key[x], prev[x] i next[x] określają jeden element listy o indeksie x. • Indeksy odgrywają rolę wskaźnika w tablicach key, next i prev
Tablicowa reprezentacja listy (danej) • W zmiennej L pamiętany jest numer pozycji (7) na której znajduje się • głowa listy. • Na liście klucz 4 znajduje się bezpośrednio za elementem o kluczu 16
Lista jednostronnie wiązana – implementacja wskaźnikowa • Definicja elementu listy • H • NIL • type stos = ^node; • node=record • val: integer; • next: stos; • end; • H • NIL
Dodawanie do stosu • procedure PUSH (x:integer); • var • pom:^node; • begin • new(pom); • pom^.val:=x; • pom^.next:=head; • head:=pom; • end; • H • NIL • pom
Dodawanie do stosu • procedure PUSH (x:integer); • var • pom:^node; • begin • new(pom); • pom^.val:=x; • pom^.next:=head; • head:=pom; • end; • H • NIL • pom
Usuwanie ze stosu • procedure POP; • var • pom:^node; • begin • if head <>NIL then • begin • pom:=head; • head:= pom^.next; • dispose(pom); • end; • end; • H • NIL • pom
Przeglądanie listy jednostronnie wiązanej • procedure ShowList; • var • pom:^node; • begin • pom:=head; • write('HEAD --> '); • while (pom<>NIL) do • begin • write(pom^.val, '--> '); • pom:=pom^.next; • end; • writeln ('NIL'); • end; • H • NIL • pom • HEAD--> • 10-->
Przeglądanie listy jednostronnie wiązanej • procedure ShowList; • var • pom:^node; • begin • pom:=head; • write('HEAD --> '); • while (pom<>NIL) do • begin • write(pom^.val, '--> '); • pom:=pom^.next; • end; • writeln ('NIL'); • end; • H • NIL • pom • HEAD--> • 10--> • -6-> • 12-->
Przeglądanie listy jednostronnie wiązanej • procedure ShowList; • var • pom:^node; • begin • pom:=head; • write('HEAD --> '); • while (pom<>NIL) do • begin • write(pom^.val, '--> '); • pom:=pom^.next; • end; • writeln ('NIL'); • end; • H • NIL • pom • HEAD--> • 10--> • -6-> • 12--> • NIL