PROJKTOWANI ALGORYTMÓW I MTODY SZTUCZNJ INTLIGNCJI TABLIC HASZUJĄC, GRAFY TABLIC HASZUJĄC Wykład dr inż. Łukasz Jeleń Na podstawie wykładów dr. T. Fevensa 3 5-6- 9-- 4 45-9-4 FUNKCJ HASZUJĄC Funkcja haszująca h mapuje klucze danego typu na integery w stałym przedziale [, N-] Przykład h(x) = x mod N jest funkcją haszującą dla kluczy (integerów) Integer h(x) jest nazywane wartością haszującą klucza x Tablica haszująca dla danego typu kluczy składa się z funkcji haszującej h Tablicy o rozmiarze N Zaprojektujemy tablicę haszującą do przechowywania wpisów (NIP, Nazwisko), gdzie NIP jest liczbowym integerem Nasza tablica haszująca wykorzystywać będzie tablice o rozmiarze N = z funkcją haszującą h(x) = ostanie cztery cyfry liczby x 3 4 999 999 9999 3-6-- 6--- 556---4-59-99-9
FUNKCJ HASZUJĄC FUNKCJ KOMPRSJI Funkcja haszującą jest zazwyczaj połączeniem dwóch funkcji Kodu haszującego: h : klucze integery Funkcji kompresji: h : integery [, N ] Początkowo stosujemy kod haszujący, a następnie nakładamy na to funkcję kompresji: h(x) = h (h (x)) Celem stosowania funkcji haszującej jest rozproszenie kluczy w pozornie losowy sposób Dzielenie: h (y) = y mod N Rozmiar tablicy haszującej N jest zazwyczaj wybierany jako liczba pierwsza Mnożenie, Dodawanie i Dzielenie: h (y) = (ay + b) mod N a (wsp. skalujący) i b (przesunięcie) są integerami takimi, że a mod N w przeciwnym wypadku każdy integer mapowałby się do b OBSŁUGA KOLIZJI Kolizje występują gdy różne elementy są mapowane do tej samej komórki linkowanie każda komórka tablicy wskazuje na listę zmapowanych tam wpisów 3 5-6-- 4 45-9--4 9---4 linkowanie jest łatwe, ale wymaga dodatkowych nakładów pamięci PRÓBKOWANI LINIOW Owarte adresowanie: kolidujące elementy są umieszczane w innej komórce tablicy Próbkowanie liniowe radzi sobie z kolizjami przez umieszczenie kolidującego elementu w kolejnej (okólnie) wolnej komórce tablicy Klucz = 3 do dodania [h(3) = 6] 4 44 59 3 3 4 5 6 9 Musimy próbkować dodatkowe dwa razy Każda komórka tablicy zbadana komórka określana jest jako próbka w powyższym przykładzie użyto trzech próbek do umieszczenia w tablicy liczby 3
PRÓBKOWANIA LINIOWGO Dana jest tablica haszująca przechowująca klucze i rozwiązująca kolizje za pomocą próbkowania liniowego N = 3 h(k) = k mod 3 Wstaw klucze, 4,, 44, 59, 3, 3, 3 (kolejność istotna) Całkowita ilość próbek: 9 Próbka k h (k ) Probes 5 5 4 9 9 44 5 5 6 59 3 6 6 3 5 5 6 9 3 9 3 4 5 6 9 4 44 59 3 3 3 PRÓBKOWANI LINIOW - PRZSZUKIWANI Dana jest tablica A wykorzystująca próbkowanie liniowe get(k) zaczynamy od komórki h(k) próbkujemy kolejne lokalizacje do momentu wystąpienia jednej z poniższych sytuacji: element o kluczu k został znaleziony znaleziono pustą komórkę Sprawdzono bez powodzenia N komórek Algorytm get(k) i h(k) p // zlicza ilość próbek repeat c A[i] if c = // pusta komórka return null else if c.key () = k return c.element() else // próbkowanie liniowe i (i + ) mod N p p + until p = N return null UAKTUALNIANI Z ZASTOSOWANIM PRÓBKOWANIA LINIOWGO W celu wykonania operacji wstawienia i usunięcia wprowadzimy specjalny obiekt, DOSTĘPNY, który zastąpi usunięte elementy remove(k) szukamy wpisu o kluczu k Jeśli taki wpis (k, v) został znaleziony, to zastępujemy go obiektem DOSTĘPNY i zwracamy wartość v w przeciwnym razie zwracamy null put(k,v) Wyrzucamy wyjątek jeśli tablica jest pełna Rozpoczynamy od komórki h(k) Próbkujemy kolejne komórki do momentu wystąpienia jednej z poniższych sytuacji: Znaleziona komórka i albo jest pusta, albo zawiera znacznik DOSTĘPNY Sprawdzono bez powodzenia N komórek Zapisujemy wpis (k, v) w komórce i Dodaj następujące integery do tablicy haszującej stosując próbkowanie liniowe {, 4,, 44, 6, 3, 3, 9} Zastosowanie: N = funkcja haszująca: h(x) = x mod 3 4 5 6 9
PODWÓJN HASZOWANI Technika podwójnego haszowania wykorzystuje drugą funkcję haszującą d(k) i rozwiązuje kolizje poprzez umieszczenie elementu w pierwszej wolnej komórce spełniającej: (i + j*d(k)) mod N gdzie i = h(k) dla j =,,, N- Druga funkcja haszująca nie może przyjmować wartości równych Rozmiar tablicy N musi być liczbą pierwszą Najczęstszym wyborem drugiej funkcji haszującej jest: d(k) = q - (k mod q) gdzie q < N q jest liczbą pierwszą Możliwe wartości dla d(k) to,,, q Dana jest tablica haszująca przechowująca klucze i rozwiązująca kolizje za pomocą podwójnego haszowania N = 3 h(k) = k mod 3 d(k) = - k mad Wstaw klucze, 4,, 44, 59, 3, 3, 3 (kolejność istotna) Całkowita ilość próbek: Próbka k h (k ) d (k ) Probes 5 3 5 4 9 6 9 44 5 5 5 59 4 3 6 3 6 3 5 4 5 9 3 4 3 4 5 6 9 3 4 3 59 3 44 3 4 5 6 9 ZŁOŻONOŚĆ HASZOWANIA W najgorszym przypadku, operacje przeszukiwania, wstawiania i usuwania na tablicach haszujących działają w czasie O(n) Najgorszy przypadek występuje w momencie kiedy wszystkie klucze umieszczane w tablicy posiadają kolizje Oczekiwany czas działania wszystkich operacji na tablicy haszującej to O() W praktyce haszowanie jest bardzo szybkie Zastosowanie tablic haszujących małe bazy danych kompilatory pamięć podręczna przeglądarek GRAFY 3 43 33
GRAFY Graf jest parą (V, ), gdzie V jest zbiorem węzłów zwanych wierzchołkami jest zbiorem par wierzchołków zwanych krawędziami Wierzchołki i krawędzie są pozycjami przechowującymi elementy Przykład: Wierzchołek reprezentuje lotnisko i przechowuje trzyliterowe kody lotnisk Krawędzie reprezentują połączenie lotnicze między dwoma lotniskami i przechowują długość połączenia HNL 555 3 43 33 3 LGA 4 99 TYPY KRAWĘDZI Krawędź skierowana skierowana para wierzchołków (u, v) pierwszy wierzchołek u jest początkiem drugi wierzchołek v jest celem/końcem np.: lot Krawędź nieskierowana nieskierowana para wierzchołków (u, v) np.: połączenie lotnicze Graf skierowany wszystkie krawędzie są skierowane np.: mapa połączeń Graf nieskierowany wszystkie krawędzie są nieskierowane lot AA 6 mil ZASTOSOWANIA TRMINOLOGIA Obwody elektroniczne płytki drukowane Sieci transportowe sieć autostrad sieć połączeń lotniczych Sieci komputerowe LAN Internet Web Bazy danych diagram związków encji cslaba cslabb math.brown.edu cs.brown.edu brown.edu qwest.net att.net cox.net John Paul David Wierzchołki końcowe krawędzi U i V są końcami krawędzi a Krawędzie incydentne do wierzchołków a, b są incydentne do V Wierzchołki sąsiednie U i V są sąsiednie Stopień wierzchołka X ma stopień 5 Krawędzie równoległe h i j są równoległe Pętla i jest pętlą U a c V W d f b e Gęstość grafu: Stosunek liczby krawędzi do max. liczby krawędzi V ( V - ) X Y g h j Z i
TRMINOLOGIA TRMINOLOGIA 3 Ścieżka sekwencja kolejnych wierzchołków i krawędzi rozpoczyna się od wierzchołka kończy się na wierzchołku każdą krawędź poprzedza i następuje jej U wierzchołek końcowy Prosta ścieżka ścieżka, w której wszystkie wierzchołki i krawędzie różnią się od siebie Przykłady: P =(V,b,X,h,Z) jest prostą ścieżką P =(U,c,W,e,X,g,Y,f,W,d,V) nie jest ścieżką prostą a c d P V W f b e P X Y g h Z Cykl okrężna sekwencja kolejnych wierzchołków i krawędzi każdą krawędź poprzedza i następuje jej wierzchołek końcowy Cykl prosty cykl, w którym wszystkie wierzchołki i krawędzie różnią się od siebie Przykłady: C =(V,b,X,g,Y,f,W,c,U,a, ) jest cyklem prostym C =(U,c,W,e,X,g,Y,f,W,d,V,a, ) nie jest cyklem prostym U a c d C V W f e b X Y C g h Z WŁAŚCIWOŚCI GŁÓWN MTODY GRAFÓW ADT Właściwość Σ v deg(v) = m Dowód: każda nieskierowana krawędź jest liczona dwa razy Właściwość W grafie nieskierowanym bez pętli i wielokrotnych krawędzi m n (n )/ Dowód: każdy wierzchołek ma stopień najwyżej (n ) Notacja n m deg(v) ilość wierzchołków ilość krawędzi stopień wierzchołka v Przykład n = 4 m = 6 deg(v) = 3 Wierzchołki i krawędzie są pozycjami przechowują elementy Metody dostępu: endvertices(e): tablica dwóch końcowych wierzchołków e opposite(v, e): przeciwległy wierzchołek do v względem e areadjacent(v, w): prawda iff v i w sąsiednie replace(v, x): zastąp element w wierzchołku v na x replace(e, x): zastąp element na krawędzi e na x Metody uaktualniające insertvertex(o): dodaj wierzchołek przechowujący element o insertdge(v, w, o): dodaj krawędź (v,w) przechowujący element o removevertex(v): usuń wierzchołek v (oraz przylegające krawędzie) removedge(e): usuń krawędź e Metody iterujące incidentdges(v): krawędzie przylegające do v vertices(): wszystkie wierzchołki w grafie edges(): wszystkie krawędzie w grafie
LISTA KRAWĘDZI LISTA SĄSIDZTWA Obiekt - wierzchołek element referencja do pozycji w liście wierzchołków Obiekt - krawędź element obiekt - wierzchołek początkowy obiekt - wierzchołek końcowy referencja do pozycji w liście krawędzi Lista wierzchołków sekwencja obiektów wierzchołka Lista krawędzi sekwencja obiektów krawędzi V u a c b d v w z u v w z a b c d Struktura listy krawędzi Lista incydencji dla każdego wierzchołka, I(v) sekwencja referencji do obiektów krawędzi krawędzi incydentnych Rozszerzone obiekty krawędzi referencje do listy sąsiedztwa wierzchołków końcowych I(u) a v b u w u v w I(w) I(v) a b V MACIRZ SĄSIDZTWA ZŁOŻONOŚĆ ASYMPTOTYCZNA Struktura listy krawędzi Rozszerzony obiekt wierzchołka klucz - integer key (indeks) - powiązany z wierzchołkiem Tablica D sąsiedztwa Referencja do obiektu krawędzi dla sąsiednich wierzchołków Null dla wierzchołków niesąsiadujących a u a u v w v b w b V n wierzchołków, m krawędzi brak krawędzi równoległych brak pętli Złożoność w notacji duże-o Lista krawędzi Lista sąsiedztwa Macierz sąsiedztwa Miejsce n + m n + m n incidentdges(v) m deg(v) n areadjacent (v, w) m min(deg(v), deg(w)) insertvertex(o) n insertdge(v, w, o) removevertex(v) m deg(v) n removedge(e)
PODGRAFY ŁĄCZNOŚĆ Podgraf S grafu G jest takim grafem, że wierzchołki S są podzbiorem wierzchołków G Podgraf rozpinający grafu G jest podgrafem, który zawiera wszystkie Podgraf Graf jest połączony (spójny) jeśli istnieje ścieżka między każdą parą wierzchołków. lementem połączonym grafu G jest maksymalny podgraf połączony grafu G Graf połączony wierzchołki G Podgraf rozpinający Graf niepołączony (niespójny) z dwoma połączonymi elementami DRZWA I LASY DRZWA I LASY ROZPINAJĄC Drzewo jest grafem nieskierowanym takim, że T jest połączone Drzewo rozpinające grafu połączonego jest podgrafem połączonym, które jest drzewem T nie zawiera cykli Jest to inna definicja drzewa niż w przypadku drzewa ukorzenionego Lasem jest graf nieskierowany bez cykli Komponenty połączone lasu są drzewami Drzewo Las Drzewo rozpinające nie jest unikalne dopóki graf nie jest drzewem Drzewa rozpinające mają zastosowanie w sieciach komunikacyjnych Las rozpinający grafu jest podgrafem rozpinającym, który jest lasem Graf Drzewo rozpinające
GRAF WAŻONY W grafie ważonym każda krawędź ma przypisaną wartość liczbową, tzw. wagę krawędzi Wagi krawędzi mogą reprezentować odległości, koszt, czas, itp. Przykład: W grafie tras lotniczych waga krawędzi reprezentuje odległość między lotniskami końcowymi wyrażoną w milach HNL 555 3 43 33 3 LGA 4 99 5 PROBLM Załóżmy, że chcemy połączyć wszystkie komputery w nowo tworzonym laboratorium/biurze minimalna ilość kabla - koszty Rozwiązanie: Model grafu ważonego (G) wierzchołki - komputery krawędzie - wszystkie możliwe pary (u, v) komputerów deg(u, v) = w(u, v) - odpowiada długości kabla potrzebnego do połączenia komputera v z komputerem u Moglibyśmy wyznaczyć najkrótszą drogę od wierzchołka v nieoptymalne Znajdziemy drzewo T, które zawiera wszystkie wierzchołki G i posiada najmniejsze łączne wagi ze wszystkich drzew rozpinających. cslaba Paul cs.brown.edu att.net cslabb cox.net brown.edu David math.brown.edu John qwest.net MINIMALN DRZWA ROZPINAJĄC Mając dany nieskierowany graf G, chcemy znaleźć drzewo T, które zawiera wszystkie wierzchołki i minimalizuje sumę: w(t) = w((v, u)) ((v, u) T) Problem wyznaczania drzewa rozpinającego o najmniejszej wadze nazywa się problemem minimalnego drzewa rozpinającego (MST). ALGORYTM KRUSKALA Buduje minimalne drzewo rozpinające z zastosowaniem klastrów grupowania węzłów Początkowo wszystkie węzły stanowią osobne klastry Krawędzie przechowywane w kolejce priorytetowej wagi są kluczami Dla wszystkich krawędzi: Q.removeMin(); Jeśli u i v nie należą do tego samego klastra to dodajemy (v, u) do T Łączymy klastry zawierające u i v w jeden Algorytm Kruskal(G) Wejście: Graf ważony G z n wierzchołkami i m krawędziami Wyjście: Minimalne drzewo rozpinające T dla grafu G for każdy wierzchołek v w G C(v) {k} Q {} // - lista krawędzi T while T.size() < n- do (u, v) Q.removeMin() if C(v) C(u) T.Add(v, u) Merge(C(v), C(u)) return T
6 6 6 6
6 6 6 6
6 6 6 6
ZŁOŻONOŚĆ ALGORYTMU KRUSKALA Możemy zaimplementować kolejkę priorytetową za pomocą kopca O(m log n) - sukcesywne wstawianie do kolejki O(m) - techniką bottom-up Usuwanie: O(log m) => O(log n) (graf jest prosty) Zatem czas to O(m log n) (pętla while) Całkowita złożoność algorytmu Kruskala: Całkowity czas pętli while: ( + deg(v))log n (v G) co daje czas O((n + m) log n) po uproszczeniu O(m log n) ALGORYTM PRIMA - JARNIKA Buduje minimalne drzewo rozpinające z zastosowaniem klastrów rozpoczyna od dowolnego wierzchołka v korzenia Idea zbliżona do algorytmu Dijkstry (później) Dodajemy v do drzewa T, a krawędzie incydentne do v umieszczamy w kolejce priorytetowej wagi są kluczami Q.removeMin(): jeśli wierzchołek z MST to dodajemy z do MST dodajemy do Q krawędzie incydentne do z jeśli T zawiera wszystkie wierzchołki grafu to koniec Algorytm PrimJarnik(G) Wejście: Graf ważony G z n wierzchołkami i m krawędziami Wyjście: Minimalne drzewo rozpinające T dla grafu G v losowy wierzchołek z G D(v) //bieżące MST for każdy wierzchołek u v D(u) + Q {} // - lista krawędzi T while Q.ismpty = false do (u, e) Q.removeMin() T.Add(u, e) for każdy wierzchołek z Q sąsiedni do u if w(u, z) < D(z) D(z) w(u,z) Wstaw (u, z) do Q z kluczem D(z) return T 6 6
6 6 6 6
6 ZŁOŻONOŚĆ 6 6 Możemy zaimplementować kolejkę priorytetową za pomocą kopca Usuwanie: O(log n) Zatem czas to O(m log n) (pętla while) Uaktualnianie: max. O(log n) dla każdej krawędzi (u, z) Pozostałe operacje wykonywane w czasie O() Zatem całkowity czas to O((n + m) log n) po uproszczeniu O(m log n)
NAJKRÓTSZA ŚCIŻKA NAJKRÓTSZA ŚCIŻKA A 4 3 B C D 5 3 9 5 F Mając dany graf ważony i dwa wierzchołki u i v chcemy wyznaczy ścieżkę między nimi o najmniejszej całkowitej wadze. Długość ścieżki jest sumą wag jej krawędzi. Przykład: Najkrótsza ścieżka między Providence i Honolulu Zastosowania Przekierowywanie pakietów Internetowych Rezerwacje lotów Wskazówki dla kierowców (np.: GPS) HNL 555 3 43 33 3 LGA 4 99 5 WŁAŚCIWOŚCI Właściwość : Podścieżka najkrótszej ścieżki jest najkrótszą ścieżką sama w sobie Właściwość : Istnieje drzewo najkrótszych ścieżek począwszy od węzła startowego do wszystkich pozostałych węzłów Przykład: Drzewo najkrótszych ścieżek z Providence HNL 555 3 43 33 3 LGA 4 99 5 ALGORYTM DIJKSTRY Odległość wierzchołka h v od wierzchołka s jest długością najkrótszej ścieżki między s and v Algorytm Dijkstra wyznacza odległości wszystkich wierzchołków począwszy od wierzchołka startowego s Założenia: graf jest spójny/połączony krawędzie są nieskierowane wagi krawędzi są nieujemne Podobnie jak w przypadku algorytmu Prima będziemy tworzyć chmurę ze wszystkich wierzchołków (MST) począwszy od s Przechowujemy każdy wierzchołek v, etykietę d(v) reprezentującą odległość v od s w podgrafie zawierającym MST wraz z sąsiednimi wierzchołkami Przy każdej iteracji Dodajemy wierzchołek u nieznajdujący się w MST o najmniejszej odległości, d(u) Uaktualniamy etykiety wierzchołków sąsiednich do u
RLAKSACJA WIRZCHOŁKÓW Weźmy krawędź e = (u,z) taką, że u jest wierzchołkiem ostatnio dodanym z jest poza MST/chmurą s d(u) = 5 u e d(z) = 5 z A 4 A 4 4 3 B C D B C D 3 9 5 3 9 5 5 F F Relaksacja krawędzi e aktualizuje odległość d(z) następująco: d(z) min{d(z),d(u) + waga(e)} s d(u) = 5 u e d(z) = 6 z A 4 3 B C D 5 3 9 5 F B A 4 C 5 3 9 F 3 D ALGORYTM DIJKSTRA A 4 3 B C D 5 3 9 5 F A 4 3 B C D 5 3 9 5 F Kolejka priorytetowa przechowuje wierzchołki nieznajdujące sie w bieżącym MST Klucz: odległość lement: wierchołek insert(k,e) zwraca lokalizację replacekey(l,k) zamienia klucz dla danego elementu Przechowujemy dwie etykiety dla każdego wierzchołka: Odległość (d(v)) lokalizacje w kolejce priorytetowej Algorithm DijkstraDistances(G, s) Q nowa kolejka priorytetowa bazująca na kopcu for all v G.vertices() if v = s setdistance(v, ) else setdistance(v, ) l Q.insert(getDistance(v), v) setlocator(v,l) while ~Q.ismpty() u Q.removeMin() for all e G.incidentdges(u) {relaksacja krawędzi e } z G.opposite(u,e) r getdistance(u) + weight(e) if r < getdistance(z) setdistance(z,r) Q.replaceKey(getLocator(z),r)
Operacje grafowe ZŁOŻONOŚĆ Metoda incidentdges jest wywoływana raz dla każdego wierzchołka Operacje etykietowania Ustawiamy/pobieramy odległość i lokalizację wierzchołka z O(deg(z)) razy Ustawianie/pobieranie etykiety zajmuje czas O() Operacje na kolejce priorytetowej Każdy wierzchołek jest umieszczany i usuwany z kolejki tylko raz. Każda taka operacja zajmuje czas O(log n) Klucz wierzchołka jest modyfikowany najwyżej deg(w) razy. Każda zmiana klucza zajmuje czas O(log n) Algorytm Dijkstra działa w czasie O((n + m) log n) pod warunkiem, że graf jest zaimplementowany za pomocą listy sąsiedztwa Po uproszczeniu O(m log n) - graf jest spójny