Algorytmy i struktury danych

Podobne dokumenty
Przykłady grafów. Graf prosty, to graf bez pętli i bez krawędzi wielokrotnych.

Struktury danych i złożoność obliczeniowa Wykład 5. Prof. dr hab. inż. Jan Magott

Grafem nazywamy strukturę G = (V, E): V zbiór węzłów lub wierzchołków, Grafy dzielimy na grafy skierowane i nieskierowane:

Ogólne wiadomości o grafach

Algorytmy grafowe. Wykład 2 Przeszukiwanie grafów. Tomasz Tyksiński CDV

Zofia Kruczkiewicz, Algorytmu i struktury danych, Wykład 14, 1

Wstęp do Programowania potok funkcyjny

Wykład 10 Grafy, algorytmy grafowe

. Podstawy Programowania 2. Algorytmy dfs i bfs. Arkadiusz Chrobot. 2 czerwca 2019

a) 7 b) 19 c) 21 d) 34

Porównanie algorytmów wyszukiwania najkrótszych ścieżek międz. grafu. Daniel Golubiewski. 22 listopada Instytut Informatyki

Algorytm DFS Wprowadzenie teoretyczne. Algorytm DFS Wprowadzenie teoretyczne. Algorytm DFS Animacja. Algorytm DFS Animacja. Notatki. Notatki.

Algorytmiczna teoria grafów

Wstęp do programowania

Algorytmy i Struktury Danych.

Wstęp do Programowania potok funkcyjny

Matematyczne Podstawy Informatyki

Wykład 7. Algorytmy grafowe

Programowanie obiektowe

Wstęp do programowania. Drzewa. Piotr Chrząstowski-Wachtel

Algorytm Dijkstry znajdowania najkrótszej ścieżki w grafie

Złożoność obliczeniowa klasycznych problemów grafowych

Digraf o V wierzchołkach posiada V 2 krawędzi, zatem liczba różnych digrafów o V wierzchołkach wynosi 2 VxV

Wykład 8. Drzewo rozpinające (minimum spanning tree)

Wykład 3. Złożoność i realizowalność algorytmów Elementarne struktury danych: stosy, kolejki, listy

Programowanie obiektowe

Algorytmy i struktury danych

Grafy i Zastosowania. 5: Drzewa Rozpinające. c Marcin Sydow. Drzewa rozpinające. Cykle i rozcięcia fundamentalne. Zastosowania

Algorytmy i Struktury Danych.

Sortowanie topologiczne skierowanych grafów acyklicznych

1. Algorytmy przeszukiwania. Przeszukiwanie wszerz i w głąb.

ĆWICZENIE 1: Przeszukiwanie grafów cz. 1 strategie ślepe

Działanie algorytmu oparte jest na minimalizacji funkcji celu jako suma funkcji kosztu ( ) oraz funkcji heurystycznej ( ).

Algorytmy Równoległe i Rozproszone Część V - Model PRAM II

E: Rekonstrukcja ewolucji. Algorytmy filogenetyczne

Wysokość drzewa Głębokość węzła

Algorytmy równoległe. Rafał Walkowiak Politechnika Poznańska Studia inżynierskie Informatyka 2010

Algorytmy i struktury danych

Algorytmy i struktury danych. Co dziś? Tytułem przypomnienia metoda dziel i zwyciężaj. Wykład VIII Elementarne techniki algorytmiczne

Podstawowe własności grafów. Wykład 3. Własności grafów

prowadzący dr ADRIAN HORZYK /~horzyk tel.: Konsultacje paw. D-13/325

3. MINIMAX. Rysunek 1: Drzewo obrazujące przebieg gry.

Drzewo. Drzewo uporządkowane ma ponumerowanych (oznaczonych) następników. Drzewo uporządkowane składa się z węzłów, które zawierają następujące pola:

Algorytmy z powracaniem

Wykłady z Matematyki Dyskretnej

Podstawowe pojęcia dotyczące drzew Podstawowe pojęcia dotyczące grafów Przykłady drzew i grafów

Rozwiązywanie problemów metodą przeszukiwania

Graf. Definicja marca / 1

Słowem wstępu. Część rodziny języków XSL. Standard: W3C XSLT razem XPath 1.0 XSLT Trwają prace nad XSLT 3.0

Algorytmy i struktury danych. Drzewa: BST, kopce. Letnie Warsztaty Matematyczno-Informatyczne

Algorytmy i Struktury Danych

Wstęp do programowania. Zastosowania stosów i kolejek. Piotr Chrząstowski-Wachtel

Indukowane Reguły Decyzyjne I. Wykład 3

Minimalne drzewa rozpinające

MATEMATYKA DYSKRETNA - MATERIAŁY DO WYKŁADU GRAFY

SZTUCZNA INTELIGENCJA

Algorytmy i Struktury Danych

Digraf. 13 maja 2017

Algorytmy i str ruktury danych. Metody algorytmiczne. Bartman Jacek

Struktury danych: stos, kolejka, lista, drzewo

Segmentacja obrazów cyfrowych z zastosowaniem teorii grafów - wstęp. autor: Łukasz Chlebda

Drzewa spinające MST dla grafów ważonych Maksymalne drzewo spinające Drzewo Steinera. Wykład 6. Drzewa cz. II

TEORETYCZNE PODSTAWY INFORMATYKI

Teoria grafów podstawy. Materiały pomocnicze do wykładu. wykładowca: dr Magdalena Kacprzak

Dynamiczny przydział pamięci w języku C. Dynamiczne struktury danych. dr inż. Jarosław Forenc. Metoda 1 (wektor N M-elementowy)

Sztuczna Inteligencja i Systemy Doradcze

Algorytmy i Struktury Danych.

Drzewa binarne. Drzewo binarne to dowolny obiekt powstały zgodnie z regułami: jest drzewem binarnym Jeśli T 0. jest drzewem binarnym Np.

ZASADY PROGRAMOWANIA KOMPUTERÓW ZAP zima 2014/2015. Drzewa BST c.d., równoważenie drzew, kopce.

Grafy (3): drzewa. Wykłady z matematyki dyskretnej dla informatyków i teleinformatyków. UTP Bydgoszcz

Reprezentacje grafów nieskierowanych Reprezentacje grafów skierowanych. Wykład 2. Reprezentacja komputerowa grafów

Matematyczne Podstawy Informatyki

7. Teoria drzew - spinanie i przeszukiwanie

G. Wybrane elementy teorii grafów

Zadanie 1: Piętnastka

TEORIA GRAFÓW I SIECI

Problem skoczka szachowego i inne cykle Hamiltona na szachownicy n x n

Algorytmy Grafowe. dr hab. Bożena Woźna-Szcześniak, prof. UJD. Wykład 5 i 6. Uniwersytet Humanistyczno-Przyrodniczy im. Jana Długosza w Częstochowie

Drzewa poszukiwań binarnych

Przypomnij sobie krótki wstęp do teorii grafów przedstawiony na początku semestru.

Znajdowanie wyjścia z labiryntu

Uniwersytet Zielonogórski Instytut Sterowania i Systemów Informatycznych. Algorytmy i struktury danych Laboratorium 7. 2 Drzewa poszukiwań binarnych

Porządek symetryczny: right(x)

Metoda podziału i ograniczeń

Temat: Struktury danych do reprezentacji grafów. Wybrane algorytmy grafowe.

Matematyczne Podstawy Informatyki

Abstrakcyjne struktury danych - stos, lista, drzewo

Wstęp do programowania

Egzaminy i inne zadania. Semestr II.

. Podstawy Programowania 2. Grafy i ich reprezentacje. Arkadiusz Chrobot. 9 czerwca 2016

Algorytmy i złożoności. Wykład 3. Listy jednokierunkowe

WYŻSZA SZKOŁA INFORMATYKI STOSOWANEJ I ZARZĄDZANIA

EGZAMIN - Wersja A. ALGORYTMY I STRUKTURY DANYCH Lisek89 opracowanie kartki od Pani dr E. Koszelew

TEORIA GRAFÓW I SIECI

Algorytmy mrówkowe. H. Bednarz. Wydział Informatyki Zachodniopomorski Uniwersytet Technologiczny w Szczecinie Inteligentne systemy informatyczne


Matematyka dyskretna

Marek Miszczyński KBO UŁ. Wybrane elementy teorii grafów 1

Podejście zachłanne, a programowanie dynamiczne

Transkrypt:

Algorytmy i struktury danych Wykład 11 - Grafy i podstawowe algorytmy grafowe (ciąg dalszy) Janusz Szwabiński Plan wykładu: Przeszukiwanie w głąb Studium przypadku - zagadnienie skoczka szachowego (ang. knight's tour problem) Algorytm przeszukiwania - wersja 1 Ogólny algorytm przeszukiwania w głąb Sortowanie topologiczne Składowe silnie spójne Najkrótsza ścieżka Algorytm Dijkstry Grafy w Pythonie - pakiet networkx Źródła: większość ilustracji i przykładów pochodzi z "Problem Solving with Algorithms and Data Structures using Python", http://interactivepython.org/runestone/static/pythonds/index.html Przeszukiwanie w głąb Studium przypadku - zagadnienie skoczka szachowego celem zagadki jest znalezienie sekwencji ruchów konika szachowego pozwalających odwiedzić wszystkie pola na szachownicy, każde tylko jeden raz na planszy o rozmiarach 3 3 można obejść osiem pól, ale na środkowe nie można już wskoczyć nie istnieje również rozwiązanie dla planszy 4 4 dla plansz kwadratowych o boku większym od 4 można znaleźć ścieżkę otwartą dla wielu istnieje również ścieżka zamknięta

zagadka to szczególny przypadek zagadnienia ścieżki Hamiltona ścieżka pozwalająca odwiedzić każdy z węzłów grafu nieskierowanego tylko raz pierwsza wzmianka o zagadce pojawiła się w 9 wieku - figura poetycka w sanskrycie górne ograniczenie na możliwe ruchy to 1.35 10 35 mimo to zagadkę można rozwiązać w liniowym czasie Rozwiązanie przy pomocy grafów stwórz reprezentację wszystkich możliwych ruchów w formie grafu znajdź ścieżki o długości liczba wierszy liczba kolumn 1 wybierz tę, w której każdy wierzchołek jest odwiedzony tylko raz Budowanie grafu węzły - pola na szachownicy krawędzie - dopuszczalne ruchy konika szachowego In [1]: from asd import Graph, Vertex In [2]: def knightgraph(bdsize): ktgraph = Graph() for row in range(bdsize): for col in range(bdsize): nodeid = postonodeid(row,col,bdsize) newpositions = genlegalmoves(row,col,bdsize) for e in newpositions: nid = postonodeid(e[0],e[1],bdsize) ktgraph.addedge(nodeid,nid) return ktgraph def postonodeid(row, column, board_size): return (row * board_size) + column

skoczek porusza się "po literze L" In [3]: def genlegalmoves(x,y,bdsize): newmoves = [] moveoffsets = [(-1,-2),(-1,2),(-2,-1),(-2,1), ( 1,-2),( 1,2),( 2,-1),( 2,1)] for i in moveoffsets: newx = x + i[0] newy = y + i[1] if legalcoord(newx,bdsize) and \ legalcoord(newy,bdsize): newmoves.append((newx,newy)) return newmoves należy sprawdzić, czy ruch nie wyprowadza poza szachownicę In [4]: def legalcoord(x,bdsize): if x >= 0 and x < bdsize: return True else: return False Przykładowa sesja: In [5]: G = knightgraph(8) In [6]: len(g.getvertices()) Out[6]: 64 In [7]: edges = 0 for v in G: for w in v.getconnections(): edges = edges + 1 print(edges) 336 64 węzły 336 krawędzi In [8]: 336/4096 Out[8]: 0.08203125

gdyby graf był zupełny, miałby 4096 krawędzi 8,2% zapełnienia macierzy sąsiedztwa Algorytm przeszukiwania - wersja 1 poznany wcześniej algorytm przeszukiwania wszerz budował drzewo przeszukiwania poziom za poziomem węzły na poziomie k + 1 były odwiedzane dopiero po zakończeniu przetwarzania wszystkich węzłów na poziomie k przeszukiwanie w głąb tworzy drzewo gałąź po gałęzi pierwsza wersja algorytmu będzie skrojona "na miarę" naszego zagadnienia - powtórne odwiedzenie węzła jest zabronione naszym celem jest stworzenie ścieżki, która w przypadku szachownicy 8 8 będzie miała długość 63 (czyli 64 wierzchołki) podobnie, jak w przypadku BFS, będziemy używali kolorów do oznaczania odwiedzonych węzłów: węzły nieodwiedzone są białe węzły odwiedzone - szare jeżeli dojdziemy do końca ścieżki, i będzie ona krótsza niż 63 ślepy zaułek musimy zapełnić możliwość powrotu (w programie poniżej to zwrócenie wartości False w wywołaniu rekurencyjnym) w BFS używaliśmy kolejki to oznaczania węzłów, które powinny być przetworzone przy rekurencyjnej implementacji DFS funkcja będzie używała niejawnie stosu systemowego nasza funkcja przyjmie 4 argumenty: n - aktualna głębokość w drzewie przeszukiwań path - lista wierzchołków odwiedzonych do tej pory u - wierzchołek, od którego startujemy limit - liczba krawędzi w ścieżce In [9]: def knighttour(n,path,u,limit): u.setcolor('gray') path.append(u) if n < limit: nbrlist = list(u.getconnections()) i = 0 done = False while i < len(nbrlist) and not done: if nbrlist[i].getcolor() == 'white': done = knighttour(n+1, path, nbrlist[i], limit) i = i + 1 if not done: # prepare to backtrack path.pop() u.setcolor('white') else: done = True return done Jak to działa? załóźmy, że funkcja getconnections zwraca węzły w porządku alfabetycznym dla grafu poniżej: n = 0

path = [] A = G.getVertex('A') limit = 5 wywołanie funkcji: knighttour(0,path,a,6) algorytm zaczyna od węzła A - oznacza go kolorem szarym i dodaje do ścieżki sąsiednie węzły to B i D ponieważ B jest przed D, DFS zaczyna od niego nasza funkcja wywołuje samą siebie z B jako punktem startowym algortym oznacza węzeł B szarym kolorem i dodaje go do ścieżki ścieżka zawiera [A,B] sprawdzani są jego sąsiedzi: C i D w kolejnym kroku przetwarzany jest C algorytm oznacza go szarym kolorem i dodaje do ścieżki nie ma on sąsiadów, a ścieżka jest krótsza niż limit: wywołanie funkcji dla C zwraca False kolor C zostaje zmieniony na biały węzeł zostaje ściągnięty ze ścieżki algorytm wraca do kolejnego sąsiada węzła B zwrócenie False przez funkcję wywołaną dla C oznacza w praktyce powrót do poprzedzającego go węzła B kolejnym sąsiadem B jest D funkcja wywołuje samą siebie dla D D oznaczony jest kolorem szarym i dodany do ścieżki aktualna zawartość ścieżki [A,B,D] D ma jednego sąsiada - E

funkcja wywołuje samą siebie dla E algorytm oznacza E kolorem szarym i dodaje go do ścieżki aktualna zawartość ścieżki [A,B,D,E] węzeł E ma dwóch sąsiadów: B i F węzeł B ma już kolor szary algorytm go pomija funkcja wywołuje samą siebie dla F algorytm oznacza F kolorem szarym i dodaje go do ścieżki aktualna zawartość ścieżki [A,B,D,E,F] F ma jednego sąsiada - C funkcja wywołuje samą siebie dla C algorytm oznacza C kolorem szarym i dodaje go do ścieżki aktualna zawartość ścieżki [A,B,D,E,F,C] warunek n < limit przestał być spełniony - algorytm kończy pracę Rozwiązanie zagadnienia skoczka In [10]: G = knightgraph(5) In [11]: path = [] s = G.getVertex(0) In [12]: s Out[12]: <asd.vertex at 0x7fdfc0107fd0> In [13]: knighttour(0,path,s,24) Out[13]: True

In [14]: print(path) [<asd.vertex object at 0x7fdfc0107fd0>, <asd.vertex object at 0x7fdfc0107d30>, <asd.vertex obje ct at 0x7fdfc0107c18>, <asd.vertex object at 0x7fdfc0117c50>, <asd.vertex object at 0x7fdfc0107 c88>, <asd.vertex object at 0x7fdfc01074a8>, <asd.vertex object at 0x7fdfc0107e10>, <asd.vertex object at 0x7fdfc0107ef0>, <asd.vertex object at 0x7fdfc0107f28>, <asd.vertex object at 0x7fdf c0107d68>, <asd.vertex object at 0x7fdfc0107b70>, <asd.vertex object at 0x7fdfc0117b00>, <asd.v ertex object at 0x7fdfc01179e8>, <asd.vertex object at 0x7fdfc0117b38>, <asd.vertex object at 0 x7fdfc0107da0>, <asd.vertex object at 0x7fdfc0107eb8>, <asd.vertex object at 0x7fdfc0107e48>, < asd.vertex object at 0x7fdfc0117ba8>, <asd.vertex object at 0x7fdfc0117ac8>, <asd.vertex object at 0x7fdfc0107cf8>, <asd.vertex object at 0x7fdfc01178d0>, <asd.vertex object at 0x7fdfc0117a9 0>, <asd.vertex object at 0x7fdfc0117be0>, <asd.vertex object at 0x7fdfc0107f60>, <asd.vertex o bject at 0x7fdfc0107cc0>] In [15]: for n in path: print(n.getid()) 0 7 14 23 16 5 2 9 12 3 6 15 22 19 8 1 10 21 18 11 20 17 24 13 4 Analiza algorytmu In [16]: import time for i in range(5,7): G = knightgraph(i) s = G.getVertex(0) path = [] start = time.time() knighttour(0,path,s,i**2-1) end = time.time() - start print('board size: {0}x{0}. Execution time: {1} s.'.format(i,end)) Board size: 5x5. Execution time: 0.03271198272705078 s. Board size: 6x6. Execution time: 72.64870166778564 s.

czas wykonania algorytmu gwałtownie rośnie analiza wykazałaby, że nasz algorytm jest klasy O(k n ), gdzie k to niewielka stała startując od korzenia, algorytm sprawdza wszystkie możliwe ruchy skoczka w zależności od położenia pola, ruchów może być od 2 do 8 drzewo przeszukiwania szybko rośnie w binarnym drzewie przeszukiwania o wysokości n liczba węzłów jest 2 n + 1 1 w naszym drzewie jest ich dużo więcej liczba potomków jest zmienna jeśli jednak k będzie średnim współczynnikiem rozgałęzienia, to liczba węzłów do odwiedzenia będzie równa k n + 1 1 Algorytm Warnsdorffa opublikowany w 1823 wybieraj jako następny ten węzeł, który ma najmniejszą możliwą liczbę dozwolonych ruchów In [17]: def orderbyavail(n): reslist = [] for v in n.getconnections(): if v.getcolor() == 'white': c = 0 for w in v.getconnections(): if w.getcolor() == 'white': c = c + 1 reslist.append((c,v)) reslist.sort(key=lambda x: x[0]) #sort the nodes by the number of connections return [y[1] for y in reslist]

In [18]: def knighttour2(n,path,u,limit): u.setcolor('gray') path.append(u) if n < limit: nbrlist = list(orderbyavail(u)) i = 0 done = False while i < len(nbrlist) and not done: if nbrlist[i].getcolor() == 'white': done = knighttour(n+1, path, nbrlist[i], limit) i = i + 1 if not done: # prepare to backtrack path.pop() u.setcolor('white') else: done = True return done In [20]: import time for i in range(5,7): G = knightgraph(i) s = G.getVertex(0) path = [] start = time.time() knighttour2(0,path,s,i**2-1) end = time.time() - start print('board size: {0}x{0}. Execution time: {1} s.'.format(i,end)) Board size: 5x5. Execution time: 0.007670402526855469 s. Board size: 6x6. Execution time: 4.006992340087891 s. Dlaczego to działa?: wybierając węzły o największej możliwej liczbie dozwolonych ruchów, skoczek dość szybko znalazłby się na środku szachownicy dochodząc ze środka do brzegu, może mieć problemy, żeby się stamtąd wydostać wybierając węzły o najmniejszej możliwej liczbie ruchów, skoczek najpierw odwiedza wszystkie pola przy brzegach trudno dostępne pola zostają odwiedzone bez większych problemów heurystyka: metoda znajdowania rozwiązań, dla której nie ma gwarancji znalezienia rozwiązania optymalnego, a często nawet prawidłowego mimo to działa w większości przypadków Ogólny algorytm przeszukiwania w głąb w zagadnieniu skoczka byliśmy zainteresowani znalezieniem jednej konkretnej ścieżki bez rozgałęzień w ogólnym przypadku najczęściej budujemy możliwie największe drzewo przeszukiwań, ze wszystkimi koniecznymi rozgałęzieniami algorytm będzie wykorzystywał dwa dodatkowe (wprowadzone już na poprzednim wykładzie) atrybuty w klasie Vertex disc - czas (liczba kroków) potrzebnych do odwiedzenia węzła po raz pierwszy fin - czas potrzebny do zmiany jego koloru na czarny dodatkowo stworzymy nową klasę, DFSGraph, która będzie dziedziczyć z klasy Graph: nowy atrybut time przechowujący liczbę kroków metoda dfs - budowanie lasu drzew DFS iteracja po wszystkich węzłach, które są białe każdy z nich staje się korzeniem nowego drzewa w ten sposób algorytm może stworzyć drzewo dla grafu niespójnego metoda dfsvisit - odwiedzanie węzłów w gałęzi rekurencyjna metoda tworzenia drzewa DFS dla pojedynczego węzła In [21]: from asd import Graph

In [22]: class DFSGraph(Graph): def init (self): super(). init () self.time = 0 def dfs(self): for avertex in self: avertex.setcolor('white') avertex.setpred(-1) for avertex in self: if avertex.getcolor() == 'white': self.dfsvisit(avertex) def dfsvisit(self,startvertex): startvertex.setcolor('gray') self.time += 1 startvertex.setdiscovery(self.time) for nextvertex in startvertex.getconnections(): if nextvertex.getcolor() == 'white': nextvertex.setpred(startvertex) self.dfsvisit(nextvertex) startvertex.setcolor('black') self.time += 1 startvertex.setfinish(self.time) Jak to działa? rozważmy prosty graf przedstawiony na poniższych rysunkach przeszukiwanie rozpoczyna się w węźle A zmieniamy jego kolor na szary ustalamy czas pierwszych odwiedzin na 1 sprawdzamy sąsiadów: B i D postanawiamy odwiedzać je w porządku alfabetycznym odwiedzamy węzeł B zmieniamy jego kolor na szary ustalamy czas pierwszych odwiedzin na 2 sprawdzamy sąsiadów: C i D odwiedzamy węzeł C zmieniamy jego kolor na szary ustalamy czas pierwszych odwiedzin na 3 sprawdzamy sąsiadów węzeł C nie ma sąsiadów: zmieniamy jego kolor na czarny

ustalamy czas zakończenia przetwarzania na 4 wracamy do węzła B i odwiedzamy kolejnego sąsiada odwiedzamy węzeł D: zmieniamy jego kolor na szary ustalamy czas pierwszych odwiedzin na 5 sprawdzamy sąsiadów: E odwiedzamy węzeł E: zmieniamy jego kolor na szary ustalamy czas pierwszych odwiedzin na 6 sprawdzamy sąsiadów: B i F węzeł B jest już szary - ignorujemy go odwiedzamy węzeł F: zmieniamy jego kolor na szary ustalamy czas pierwszych odwiedzin na 7 sprawdzamy sąsiadów: C ponieważ C jest już czarny (zakończył jakąś gałąź), algorytm doszedł do końca kolejnej gałęzi zmieniamy kolor F na czarny ustalamu czas zakończenia na 8 wracamy do poprzednika - E E nie ma więcej białych sąsiadów: zmieniamy kolor na czarny ustalamy czas zakończenia na 9 wracamy do poprzednika - D D nie ma więcej białych sąsiadów:

zmieniamy kolor na czarny ustalamy czas zakończenia na 10 wracamy do poprzednika - B B nie ma więcej białych sąsiadów: zmieniamy kolor na czarny ustalamy czas zakończenia na 11 wracamy do poprzednika - A A nie ma więcej białych sąsiadów: zmieniamy kolor na czarny ustalamy czas zakończenia na 12 A nie ma poprzednika - koniec działania programu Wynikowe drzewo wyszukiwania każde dziecko ma późniejszy czas pierwszych odwiedziń i wcześniejszy zakończenia od swojego rodzica Analiza czasu wykonywania pętle w metodzie dfs to O(V), gdzie V to liczba węzłów dfvisit jest uruchamiane raz dla każdej krawędzi na liście sąsiedztwa: ponieważ funkcja odwiedza tylko węzły białe, wykonywana jest co najwyżej raz dla każdej krawędzi klasa O(E), gdzie E to liczba krawędzi sumaryczna złożoność: O(V + E)

Sortowanie topologiczne specjaliści potrafią przekształcić wiele zagadnień na problem z dziedziny grafów przykład - przepis kuchenny na naleśniki: zakładając, że dysponujemy tylko takim grafem, nasuwa się pytanie, od którego węzła i w jakiej kolejności wykonać wszystkie kroki sortowanie topologiczne skierowanego grafu acyklicznego: liniowe uporządkowanie wierzchołków, w którym jeśli istnieje krawędź skierowana prowadząca od wierzchołka x do y, to x znajdzie się przed wierzchołkiem y innymi słowy, każdy wierzchołek poprzedza wszystkie te wierzchołki, do których prowadzą wychodzące od niego krawędzie do wykonania sortowania topologicznego możemy zaadaptować przeszukiwanie w głąb: wywołujemy metodę dfs dla danego grafu G - celem jest wyznaczenie czasów odwiedzin i zakończenia przetwarzania węzłów węzły porządkujemy według malejących czasów wykonania uporządkowane węzły są posortowane topologicznie Składowe silnie spójne

Motywacja Składowa silnie spójna Maksymalny podgraf H grafu G, a jednocześnie jego spójna składowa, taka, że pomiędzy każdymi dwoma jej wierzchołkami istnieje ścieżka. Składowa spójna Spójny podgraf grafu G nie zawarty w większym podgrafie spójnym grafu G. Graf spójny Graf spełniający warunek, że dla każdej pary wierzchołków istnieje ścieżka, która je łączy. po zidentyfikowaniu składowych silnie spójnych możemy uprościć reprezentację grafu

Dygresja - transpozycja grafu transpozycja G T grafu G to graf powstały przez odrócenie kierunku wszystkich krawędzi w grafie G zauważmy, że transpozycja nie zmienia liczby składowych silnie spójnych Algorytm wyszukiwania składowych silnie spójnych uruchom metodę dfs dla grafu G w celu wyznaczenia czasów wyznacz G T uruchom dfs dla grafu G T : w głównej pętli metody odwiedzaj węzły według malejącego porządku czasów końcowych każde z drzew wyznaczonych przez dfs na grafie transponowanym to składowa silnie spójna Jak to działa? krok 1 - uruchamiamy metodę dfs na grafie wejściowym krok 2 - uruchamiamy metodę na grafie transponowanym, pamiętając o specjalnym porządku odwiedzania węzłów (czasy na poniższym rysunku to nowe czasy przetwarzania na grafie transponowanym)

krok 3 - identyfikujemy poszczególne drzewa

Najkrótsza ścieżka Motywacja każdy router połączony jest z innymi routerami każdemu połaczeniu między routerami przypisany jest koszt połączenia (związany z wolumenem danych przesyłanych przez łacze, porą dnia itp.) chcemy znaleźć ścieżkę z najmniejszym całkowitym kosztem połączenia problem podobny do przeszukiwania wszerz, jednak teraz bardziej liczy się waga niż liczba przeskoków Algorytm Dijkstry podobnie, jak w BFS, wykorzystamy atrybut dist w klasie Vertex do pomiaru długości jednak teraz nie będzie to liczba przeskoków, a całkowity koszt ścieżki algorytm przechodzi przez każdy węzeł w grafie kolejność przechodzenia sterowana jest przy pomocy kolejki prorytetowej (zbudowanej na kopcu) priorytetem będzie atrybut dist, podczas tworzenia węzła ustawiony na bardzo dużą wartość In [2]: from asd import PriorityQueue, Graph, Vertex def dijkstra(agraph,start): pq = PriorityQueue() start.setdistance(0) pq.buildheap([(v.getdistance(),v) for v in agraph]) while not pq.isempty(): currentvert = pq.delmin() for nextvert in currentvert.getconnections(): newdist = currentvert.getdistance() + currentvert.getweight(nextvert) if newdist < nextvert.getdistance(): nextvert.setdistance( newdist ) nextvert.setpred(currentvert) pq.decreasekey(nextvert,newdist) kolejka priorytetowa przechowuje krotki klucz-wartość klucz to klucz węzła, wartość to odległość do węzła decreasekey - zmienia odległośc do węzła, który jest jeszcze w kolejce Jak to działa? zacznijmy od wierzchołka u

ustawiamy jedo odległość na 0 sprawdzamy sąsiadów: x, v, w odległości wszystkich sąsiadów są początkowo ustawione na sys.maxint kosztem ich osiągnięcia jest zatem koszt ich bezpośredniego osiągnięcia aktualizujemy ich odległości, przypisując im koszt związany z krawędziami najmniejszy koszt ma węzeł x odwiedzamy węzeł x sąsiedzi to: u, v, w, y dla każdego węzła sprawdzamy, czy dojście do niego poprzez węzeł x będzie tańsze od jego aktualnego kosztu będzie to prawdą dla y, który ma odległość ustawioną na sys.maxint również droga do w będzie mniejsza, jeśli pójdziemy do niego poprzez x aktualizujemy koszt w zmieniamy jego poprzednika z u na x odwiedzamy węzeł v ten krok nie zmienia nic w grafie możemy przejść do węzła y odwiedzamy węzeł y okazuje się, że poprzez y można dotrzeć taniej do w i z zmieniamy koszty tych węzłów

ustawiamy ich poprzedników na y odwiedzamy węzeł w brak zmian w grafie odwiedzamy węzeł z brak zmian w grafie Ważne algorytm działa tylko w przypadku nieujemnych wag nie nadaje się do badania Internetu, ponieważ wymaga jego pełnego grafu każdy router musiałby przechowywać informacje o wszystkich innych routerach na świecie w przypadku Internetu używa się innych algorytmów, np. algorytmu wektora odległości (https://pl.wikipedia.org/wiki/algorytm_wektora_odleg%c5%82o%c5%9bci) Złożoność budowanie kolejki priorytetowej: O(V) (początkowo każdy węzeł jest w kolejce) pętla while wykonywana jest dla każdego węzła raz wewnątrz tej pętli delmin to O(logV) pętla jest rzędu O(VlogV)

pętla for wykonywana jest raz dla każdej krawędzi: decreasekey jest rzędu O(logV) pętla jest rzędu O(ElogV) całość jest rzędu O((V + E)logV) Grafy w Pythonie - pakiet networkx In [3]: %matplotlib inline import networkx as nx Tworzenie grafu In [4]: G = nx.graph() In [5]: G Out[5]: <networkx.classes.graph.graph at 0x7fcc64efc4e0> Węzły dodajemy pojedynczo: In [6]: G.add_node(1) lub całymi listami: In [7]: G.add_nodes_from([2,3,4,5,6,7,8,9]) In [8]: G.nodes() Out[8]: [1, 2, 3, 4, 5, 6, 7, 8, 9] Podobnie z krawędziami: In [9]: G.add_edge(1,2) e=(2,3) G.add_edge(*e) # unpack edge tuple* G.add_edges_from([(3,6),(3,9),(1,7),(2,6),(5,6),(1,8)]) In [10]: G.nodes() Out[10]: [1, 2, 3, 4, 5, 6, 7, 8, 9] In [11]: G.edges() Out[11]: [(1, 8), (1, 2), (1, 7), (2, 3), (2, 6), (3, 9), (3, 6), (5, 6)]

In [12]: G.neighbors(6) Out[12]: [2, 3, 5] Atrybuty węzłów krawędzi i grafu In [13]: G.add_node(10, time='5pm') G.node[10]['room'] = 714 G.nodes(data=True) Out[13]: [(1, {}), (2, {}), (3, {}), (4, {}), (5, {}), (6, {}), (7, {}), (8, {}), (9, {}), (10, {'room': 714, 'time': '5pm'})] In [14]: G.graph['day']='Monday' G.graph Out[14]: {'day': 'Monday'} In [15]: G[1][2]['weight'] = 4.7 G.edge[5][6]['weight'] = 4 In [16]: G.edges(data=True) Out[16]: [(1, 8, {}), (1, 2, {'weight': 4.7}), (1, 7, {}), (2, 3, {}), (2, 6, {}), (3, 9, {}), (3, 6, {}), (5, 6, {'weight': 4})] Grafy skierowane In [17]: DG=nx.DiGraph() DG.add_weighted_edges_from([(1,2,0.5), (3,1,0.75)]) In [18]: DG.out_degree(1,weight='weight') Out[18]: 0.5

In [19]: DG.degree(1,weight='weight') Out[19]: 1.25 In [20]: DG.successors(1) Out[20]: [2] In [21]: DG.neighbors(1) Out[21]: [2] Generatory grafów In [22]: er=nx.erdos_renyi_graph(20,0.15) ws=nx.watts_strogatz_graph(30,3,0.1) ba=nx.barabasi_albert_graph(20,5) red=nx.random_lobster(20,0.9,0.9) Wizualizacja In [23]: nx.draw(er)

In [24]: nx.draw_circular(er) In [25]: nx.draw(ws)

In [26]: nx.draw(ba) In [27]: nx.draw(red) Analiza grafów

In [28]: nx.draw(g,with_labels=true) In [29]: list(nx.connected_components(g)) Out[29]: [{1, 2, 3, 5, 6, 7, 8, 9}, {4}, {10}] In [30]: nx.clustering(g) Out[30]: {1: 0.0, 2: 0.3333333333333333, 3: 0.3333333333333333, 4: 0.0, 5: 0.0, 6: 0.3333333333333333, 7: 0.0, 8: 0.0, 9: 0.0, 10: 0.0} In [31]: nx.degree(g) Out[31]: {1: 3, 2: 3, 3: 3, 4: 0, 5: 1, 6: 3, 7: 1, 8: 1, 9: 1, 10: 0} In [32]: sorted(nx.degree(g).values()) Out[32]: [0, 0, 1, 1, 1, 1, 3, 3, 3, 3] Przeszukiwanie wszerz In [33]: G = nx.barabasi_albert_graph(50,4)

In [34]: print(g.nodes()) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49] In [35]: print(g.edges()) [(0, 4), (1, 17), (1, 18), (1, 4), (1, 5), (1, 24), (1, 8), (1, 41), (1, 26), (1, 12), (2, 34), (2, 4), (2, 5), (2, 6), (2, 7), (2, 9), (2, 44), (2, 14), (2, 48), (2, 46), (2, 23), (2, 25), (2, 30), (2, 31), (3, 32), (3, 17), (3, 18), (3, 4), (3, 5), (3, 6), (3, 49), (3, 24), (3, 20), (3, 47), (3, 15), (4, 5), (4, 6), (4, 7), (4, 8), (4, 11), (4, 12), (4, 46), (4, 15), (4, 48), (4, 49), (4, 25), (4, 28), (5, 32), (5, 6), (5, 7), (5, 8), (5, 9), (5, 10), (5, 11), (5, 12), (5, 13), (5, 19), (5, 20), (5, 21), (5, 23), (6, 33), (6, 35), (6, 7), (6, 9), (6, 10), (6, 45 ), (6, 16), (6, 49), (6, 18), (6, 19), (6, 22), (6, 26), (6, 29), (6, 30), (7, 8), (7, 42), (7, 14), (7, 15), (7, 16), (7, 22), (7, 28), (7, 37), (8, 34), (8, 43), (8, 9), (8, 10), (8, 11), (8, 12), (8, 13), (8, 48), (8, 17), (8, 20), (8, 21), (8, 36), (8, 26), (8, 28), (8, 42), (8, 3 9), (9, 32), (9, 10), (9, 11), (9, 44), (9, 40), (9, 19), (9, 21), (9, 22), (9, 27), (9, 31), ( 10, 34), (10, 28), (10, 29), (10, 30), (11, 34), (11, 35), (11, 38), (11, 41), (11, 13), (11, 1 4), (11, 21), (12, 32), (12, 38), (12, 23), (12, 13), (13, 47), (13, 14), (13, 15), (13, 16), ( 13, 17), (13, 23), (13, 27), (13, 31), (14, 48), (14, 40), (14, 46), (14, 16), (14, 18), (14, 2 2), (14, 25), (14, 29), (15, 27), (16, 20), (16, 39), (18, 36), (18, 33), (18, 39), (18, 44), ( 18, 47), (18, 43), (18, 19), (18, 46), (18, 24), (18, 26), (18, 30), (19, 36), (19, 39), (20, 3 5), (20, 38), (21, 27), (21, 41), (22, 37), (22, 40), (22, 42), (23, 24), (23, 25), (24, 37), ( 25, 47), (27, 45), (27, 41), (27, 31), (28, 42), (28, 40), (28, 35), (28, 36), (28, 43), (28, 2 9), (30, 33), (30, 37), (31, 33), (32, 38), (38, 45), (39, 49), (39, 43), (39, 44), (43, 45)] In [36]: bfs = nx.bfs_edges(g,9) In [37]: print(list(bfs)) [(9, 32), (9, 2), (9, 5), (9, 6), (9, 8), (9, 10), (9, 11), (9, 44), (9, 40), (9, 19), (9, 21), (9, 22), (9, 27), (9, 31), (32, 3), (32, 12), (32, 38), (2, 34), (2, 4), (2, 7), (2, 14), (2, 48), (2, 46), (2, 23), (2, 25), (2, 30), (5, 1), (5, 13), (5, 20), (6, 33), (6, 35), (6, 45), ( 6, 16), (6, 49), (6, 18), (6, 26), (6, 29), (8, 43), (8, 17), (8, 36), (8, 28), (8, 42), (8, 39 ), (11, 41), (22, 37), (27, 15), (3, 24), (3, 47), (4, 0)] In [38]: tree = nx.bfs_tree(g,9) test2

In [39]: nx.draw_spring(tree,with_labels=true) In [40]: from networkx.drawing.nx_agraph import graphviz_layout nx.draw(tree,pos=graphviz_layout(tree),with_labels=true,prog='dot') Przeszukiwanie w głąb In [41]: dfs = nx.dfs_edges(g,9)

In [42]: print(list(dfs)) [(9, 32), (32, 3), (3, 17), (17, 8), (8, 1), (1, 18), (18, 36), (36, 19), (19, 5), (5, 2), (2, 34), (34, 10), (10, 6), (6, 33), (33, 30), (30, 37), (37, 24), (24, 23), (23, 25), (25, 47), (4 7, 13), (13, 11), (11, 35), (35, 20), (20, 16), (16, 7), (7, 4), (4, 0), (4, 12), (12, 38), (38, 45), (45, 43), (43, 28), (28, 42), (42, 22), (22, 40), (40, 14), (14, 48), (14, 46), (14, 29), (43, 39), (39, 49), (39, 44), (45, 27), (27, 21), (21, 41), (27, 15), (27, 31), (6, 26)] In [43]: dtree = nx.dfs_tree(g,9) In [44]: nx.draw(dtree,pos=graphviz_layout(dtree),with_labels=true,prog='dot')