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')