Zbiory dynamiczne
Zbiór dynamiczny
Zbiór dynamiczny to zbiór wartości
pochodzących z pewnego określonego
uniwersum, którego zawartość zmienia
się w trakcie działania programu.
Elementy zbioru dynamicznego musimy
co najmniej umieć porównać pod
względem identyczności (czy dwa
elementy są równe albo różne).
Dynamiczne struktury danych
Dynamiczna struktura danych, to struktura
danych pozwalająca na przechowywanie zbioru
dynamicznego; rozmiar tej struktury dostosowuje
się do rozmiaru danych.
W zbiorze dynamicznym musimy umieć
realizować operację dodania nowego elementu
do zbioru i usunięcia ze zbioru wskazanego
elementu.
Tablica dynamiczna jest dynamiczną strukturą
danych.
Zastosowanie: przechowywanie pewnego zbioru
danych, którego zawartość będzie się zmieniać w
trakcie pracy programu.
Słownik
Słownik (ang. dictionary) to struktura danych
pozwalająca efektywnie realizować następujące
operacje:
◦ insert(x) – dodanie nowego elementu x do zbioru
dynamicznego,
◦ delete(x) – usunięcie elementu o wartości x ze zbioru
dynamicznego,
◦ search(x) – sprawdzenie czy w zbiorze dynamicznym
znajduje się element o wartości x.
Multizbiór to zbiór dynamiczny, w którym mogą
się powtarzać elementy o takich samych
wartościach.
Struktura danych jest homogeniczna, jeśli składa
się z elementów tego samego typu.
Listy
Lista
Lista (ang. list) to homogeniczna struktura danych służąca do
reprezentowania zbioru dynamicznego, w której elementy
ułożone w ciąg.
Element listy nazywa się węzłem (ang. node); każdy węzeł
zawiera pole info służące do przechowywania wartości z
pewnego określonego uniwersum oraz pole next ze
wskaźnikiem na następny element listy (ostatni element listy
ma w polu next wpisany wskaźnik pusty).
Pierwszy węzeł listy jest nazywany głową (ang. head) albo
początkiem listy.
Dostęp do elementów listy jest sekwencyjny – a więc dojście
do elementu k-tego wymaga przejścia przez kolejne
elementy listy od pierwszego do docelowego.
Zastosowanie: lista najlepiej nadaje się do danych, które
będą przetwarzane sekwencyjnie.
Lista
Lista jednokierunkowa (ang. single linked
list) to lista, po której można się poruszać
tylko od głowy do ogona – w każdym
węźle jest tylko wskaźnik do następnika.
Lista dwukierunkowa (ang. double linked
list) to lista, po której można się poruszać
w obu kierunkach: w stronę głowy i w
stronę ogona – w każdym węźle są dwa
wskaźniki next do następnika i prev do
poprzednika.
Lista
Lista cykliczna to lista, w której ostatni węzeł
posiada wskaźnik do pierwszego węzła.
Lista dwukierunkowa może być cykliczna.
Lista z wartownikiem to lista, w której na końcu
umieszczony jest węzeł zwany wartownikiem –
wartownik nie przechowuje danych, pełni rolę
pomocniczą w nawigacji po liście.
Lista z wartownikiem może być cykliczna lub
dwukierunkowa.
Gdy dane pochodzą z uniwersum z porządkiem
liniowym, to dane w liście można przechowywać
w sposób uporządkowany – mamy w tedy do
czynienia z listą uporządkowaną.
Lista
Wyszukiwanie wartości w liście – wersja iteracyjna
Search(wezeł *w, x) -> boolean
{
while (w.info != x) {
if (w.next != null) w := w.next;
else return false;
}
return true;
}
Czas: O(n) gdzie n to ilość elementów na liście
Pamięć: O(1)
Lista
Wyszukiwanie wartości w liście – wersja rekurencyjna
Search(wezeł *w, x) -> boolean
{
if (w == null) return false;
if (w.info == x) return true;
return Search(w.next, x);
}
Czas: O(n) gdzie n to ilość elementów na liście
Pamięć: O(n) zależy od liczby wywołań rekurencyjnych
Lista
Wyszukiwanie wartości w liście posortowanej – wersja
rekurencyjna
Search(wezeł *w, x) -> boolean
{
if (w == null) return false;
if (w.info == x) return true;
if (w.info > x) return false;
return Search(w.next, x);
}
Czas: O(n) gdzie n to ilość elementów na liście
Pamięć: O(n) zależy od liczby wywołań rekurencyjnych
Lista
Wstawianie elementu do listy
nieuporządkowanej:
◦ na początek listy,
◦ na koniec,
◦ na zadaną pozycję.
Wstawianie elementu do listy
uporządkowanej
Usuwanie elementu z listy:
◦ usuwanie elementu z zadanej pozycji,
◦ usuwanie elementu o zadanej wartości.
Listy
Technika zwracania wskaźnika do struktury po zmodyfikowaniu.
Przykład: wstawienie elementu na zadaną pozycję:
insert(węzeł *w, x, pos) -> węzeł*
{
if (pos < 0) error;
if (w == null and pos > 0) error;
if (pos > 0) {
w.next := insert(w.next, x, pos-1);
return w;
}
else return new węzeł(x, w);
}
wywołanie:
head := insert(head, x);
Listy
Operacje słownikowe na liście nelementowej wymagają:
◦ czasu O(n),
◦ pamięci O(1) gdy używamy iteracji albo O(n)
gdy korzystamy z rekurencji.
Listy
Zadanie: podział listy na dwie równe podlisty (z dokładnością do 1
elementu).
Rozwiązanie: dwa wskaźniki, jeden robi podwójne skoki, drugi
pojedyncze; po dotarciu na koniec listy pierwszego wskaźnika, drugi
wskazuje na węzeł środkowy.
split(węzeł *h) -> (węzeł*, węzeł*)
{
węzeł *p = head;
węzeł *q = head;
if (p == null) return (null, null);
while (q != null) {
q := q.next;
if (q == null) break;
q := q.next;
if (q != null) p := p.next;
}
q := p.next;
p.nex; := null;
return (head, q);
}
Listy
Zadanie: należy scalić dwie posortowane lisy .
Rozwiązanie: do końca listy wynikowej doczepiamy mniejszy spośród głów pozostałych
list.
merge(węzeł *g, węzeł *h) -> węzeł*
{
if (g == null) return h;
if (h == null) return g;
węzeł *r = g;
if (g.info < h.info) g := g.next;
else { r := h; h := h.next; }
węzeł *s = r;
while (g != null and h != null)
if (g.info < h.info) {
r.next := g;
r := r.next;
g := g.next;
}
else {
r.next := h;
r := r.next;
h := h.next;
}
if (g != null) r.next := g;
else r.next := h;
return s;
}
Zadanie 1
Zdefiniuj klasę reprezentującą węzeł listy
jednokierunkowej wraz z operacjami
wstawiania nowego elementu na zadaną
pozycję, usuwania węzła z zadanej pozycji
i sprawdzania czy określony element
znajduje się na liście (operacje te
zaimplementuj rekurencyjnie).
Węzeł listy zdefiniuj jako szablon przy
pomocy template<typename T>.
Zadanie 2
Zdefiniuj klasę reprezentującą listę jako
opakowanie dla struktury zbudowanej na
węzłach. W klasie tej opakuj metodę
wstawiającą do listy, usuwającą z listy i
sprawdzającą czy element występuje na
liście. Dodatkowo dopisz metody:
wstawiająca element na początek listy, na
koniec listy, usuwającą pierwszy i usuwającą
ostatni element na liście.
Listę zdefiniuj jako szablon przy pomocy
template<typename T>.
Zadanie 3
Zdefiniuj klasę reprezentującą węzeł listy
jednokierunkowej uporządkowanej wraz z
operacjami wstawiania nowego elementu,
usuwania elementu i sprawdzania czy określony
element znajduje się na liście.
Zdefiniuj też klasę reprezentującą listę jako
opakowanie dla struktury zbudowanej na
węzłach.
Węzeł listy oraz listę zdefiniuj jako szablony przy
pomocy template<typename T>.
Drzewa
Drzewa
Drzewo (ang. tree) to zbiór węzłów powiązanych
wskaźnikami, bez cykli.
Drzewo ukorzenione – posiada wyróżniony węzeł
początkowy zwany korzeniem (ang. root).
Drzewo jest strukturą hierarchiczną – analogia do
polskich jabłek na rozgałęzionej jabłoni.
Korzeń węzła znajduje się na poziomie 0; numer
poziomu danego węzła w drzewie jest wyznaczony
odległością krawędziową od korzenia.
Liściem w drzewie (ang. leaf) jest węzeł bez żadnego
następnika.
Węzeł wewnętrzny posiada co najmniej jednego
następnika.
Drzewa
Wysokość drzewa – długość najdłuższej
ścieżki od korzenia do liścia (liczba
węzłów) – inaczej liczba poziomów na
których zapisane jest drzewo.
Głębokość węzła – długość ścieżki od
korzenia do tego węzła (liczba węzłów) –
inaczej numer poziomu na którym
znajduje się węzeł.
Uwaga: poziomy w drzewie numerujemy
od 0; korzeń znajduje się na poziomie 0.
Drzewo
Drzewo uporządkowane ma
ponumerowanych (oznaczonych)
następników – ich kolejność w takim
drzewie jest istotna.
Drzewo k-arne to drzewo
uporządkowane, które posiada co
najwyżej k następników.
Drzewo binarne – może posiadać dwóch
następników: lewego i prawego.
Reprezentacja drzew uporządkowanych
„na lewo syn na prawo brat”
T.root
/
/
/
/
/
/
/
/
/
/
/
/
/
Drzewo BST
Drzewo BST to drzewo binarne, w którym
przechowujemy elementy z pewnego
uniwersum z porządkiem liniowym i w
drzewie tym jest zachowany porządek
symetryczny.
W drzewie binarnym jest zachowany
porządek symetryczny, gdy elementy
mniejsze od korzenia znajdują się w lewym
poddrzewie, elementy większe od korzenia
w prawym poddrzewie oraz w lewym i w
prawym poddrzewie też jest zachowany
porządek symetryczny.
Drzewo BST
Wyszukiwanie w drzewie BST – działa tak jak
w wyszukiwaniu binarnym
serach (węzeł *w, x) -> boolean
{
if (w == null) return false;
if (x < w.info)
return search(w.left, x);
else if (w.info < x) then
return search(w.right, x);
else return true;
}
Drzewo BST
Element minimalny znajduje się
najbardziej po lewej stronie w drzewie
BST – od korzenia poruszamy się ciągle w
lewo, ostatni węzeł na lewej ścieżce
zawiera minimum.
Element maksymalny znajduje się
najbardziej po prawej stronie w drzewie
BST – od korzenia poruszamy się ciągle w
prawo , ostatni węzeł na prawej ścieżce
zawiera maksimum.
Drzewo BST
Wstawienie nowego elementu do drzewa BST –
znajdujemy mu miejsce tak jak w wyszukiwaniu
binarnym (aż dojdziemy do wskaźnika pustego)
insert (węzeł *w, x) -> node*
{
if (w == null) return new node(x);
if (x < w.info)
w.left := insert(w.left, x);
else if (w.info < x) then
w.right := insert(w.right, x);
else w.info := x;
return w;
}
Drzewo BST
Usunięcie elementu z drzewa BST –
znajdujemy miejsce tego elementu tak jak w
wyszukiwaniu binarnym (jak dojdziemy do
wskaźnika pustego to elementu nie ma w
drzewie):
◦ gdy element jest w liściu, to odcinam liść;
◦ gdy element jest w węźle z jednym następnikiem,
to usuwam ze ścieżki ten węzeł;
◦ gdy element jest w węźle z dwoma synami, to z
prawego poddrzewa usuwamy minimum (albo z
lewego poddrzewa usuwamy maksimum) i
przenosimy je do tego węzła.
Przeglądanie drzew BST
In-order – najpierw jest przeglądane i
przetwarzane lewe poddrzewo w porządku
in-order, potem korzeń a na końcu prawe
poddrzewo w porządku in-order.
Przeglądanie drzewa BST w porządku inorder gwarantuje, że węzły tego drzewa
zostaną przetworzone w kolejności od
najmniejszej do największej wartości.
Przeglądanie in-order można wykorzystać do
sortowania danych.
Przeglądanie drzew BST
Pre-order – najpierw jest przeglądany i przetwarzany
korzeń drzewa, potem lewe poddrzewo w porządku
pre-order a na końcu prawe poddrzewo w porządku
pre-order.
Przeglądanie drzewa BST w porządku pre-order
gwarantuje, że na początku będą przetworzone węzły
z lewej ścieżki tego drzewa w kolejności od korzenia
do węzła o najmniejszej wartości.
Post-order – najpierw jest przeglądane i przetwarzane
lewe poddrzewo w porządku post-order, potem
prawe poddrzewo w porządku post-order a na końcu
korzeń drzewa.
Przeglądanie drzewa BST w porządku post-order
gwarantuje, że na końcu będą przetworzone węzły z
prawej ścieżki tego drzewa w kolejności od węzła o
największej wartości do korzenia.
Struktura drzew BST
Wysokość drzewa – liczba poziomów
zajmowanych przez drzewo (korzeń znajduje
się na poziomie 0, na ostatnim poziomie
znajdują się tylko liście) – albo długość
wierzchołkowa najdłużej ścieżki od korzenia
do liścia .
Głębokość węzła – numer poziomu na
którym znajduje się dany węzeł – albo
krawędziowa odległość od korzenia.
Wysokość drzewa n-elementowego jest nie
mniejsza niż log(n) i nie większa niż n.
Struktura drzew BST
Drzewo regularne to drzewo binarne, w
którym każdy węzeł wewnętrzny na dwóch
synów.
Drzewo pełne to drzewo regularne, w
którym wszystkie liście są na tym samym
poziomie.
Drzewo pełne o wysokości h ma 2h-1
węzłów.
Drzewo zupełne to drzewo pełne, z którego
usunięto część liści z prawej strony.
Drzewo zupełne o wysokości h ma od 2h-1 do
2h-1 węzłów.
Rotacje w drzewie BST
W dowolnym wewnętrznym węźle drzewa
BST można zrobić rotację (w lewo albo w
prawo) i nie zostanie zniszczony porządek
symetryczny w tym drzewie.
Zadanie 1
Zdefiniuj klasę reprezentującą węzeł
drzewa BST wraz z operacjami wstawiania
nowego elementu, usuwania elementu i
sprawdzania czy określony element
znajduje się w drzewie.
Węzeł drzewa zdefiniuj jako szablon przy
pomocy template<typename T>.
Zadanie 2
Zdefiniuj klasę reprezentującą drzewo BST
jako opakowanie dla struktury zbudowanej
na węzłach. W klasie tej opakuj metodę
wstawiającą do drzewa, usuwającą z drzewa
i sprawdzającą czy element występuje w
drzewie. Dodatkowo dopisz metody:
wyznaczające oraz usuwające najmniejszy i
największy element w drzewie.
Drzewo BST zdefiniuj jako szablon przy
pomocy template<typename T>.