Graf : przykład projektowania klasy¶
Rozpatrzmy strukturę danych grafu jako przykład projektowania klasy. Graf teoretycznie definiujemy jako zbiór wierzchołków \({V}\) oraz zbiór łączących je krawędzi \({E}\). Wydawało by się, że wystarczy ponumerować wierzchołki i przechowywać krawędzie
1class SimpleGraph:
2
3 def __init__(self):
4 pass
5
6 def add_edge(self,vi,vj):
7 pass
8
9 def are_connected(self,vi,vj):
10 pass
11
12 def get_edge(self, vi, vj):
13 pass
14
15 def get_vertices(self,vi):
16 pass
17
18if __name__ == "__main__":
Szybko jednak dojdziemy do wniosku, że przydatne byłoby również przechowywanie informacji dla wierzchołków:
1class SimpleGraph:
2
3 def __init__(self):
4 pass
5
6 def add_edge(self,vi,vj):
7 pass
8
9 def are_connected(self,vi,vj):
10 pass
11
12 def get_edge(self, vi, vj):
13 pass
14
15 def get_edges(self, vi):
16 pass
17
18 def get_vertices(self,vi):
19 pass
20
21 def add_vertex(self,vi):
22 pass
23
24if __name__ == "__main__":
25
26 g = SimpleGraph()
27 for vi in ['A','B','C','D','E'] :
28 g.add_vertex(vi)
29 g.add_edge('A','B', "blue")
30 g.add_edge(4, 3, "red")
Powyższy kod pokazuje, jakie metody chcielibymy mieć w klasie SimpleGraph. Kwestia implementacji pozostaje jednak otwarta.
W zasadzie są dwie możliwości: jako macierz i jako listy
Graf jako macierz sąsiedztwa¶
W pierwszej z tych implementacji mamy dwuwymiarową macierz E[][], w której E[i][j]==0 oznacza, że wierzchołki
i oraz j nie są połączone. Krawędzie grafu mogą być niezerowymi elementami tej macierzy, np. E[i][j] = "blue".
Dane dla wierzchołków przechowywać wtedy można na liście.
Note
Jakie są wady takiego rozwiązania? A jakie zalety?
Graf jako lista sąsiedztwa¶
Po przedyskutowaniu wad i zalet, wybieramy implementację list sąsiedztwa. Pojawia się przy tym kolejne pytanie. Otóż krawędzie można przechowywać w liście list:
1edges = [ [3], [2, 3], [1], [0,2] ]
lub w słowniku:
1edges = { 0: [3], 1: [2, 3], 2: [1], 3:[0, 2] }
Wybór zależy od tego, ile składowych będzie miał nasz graf. Powyżej już przyjęliśmy, że będziemy przechowywać indeksy powiązanych wierzchołków, choć można by trzymać same wierzchłoki, jak poniżej:
1edges = { 'A': ['C'], 'B': ['C', 'D'], 'C': ['B'], 'D':['A', 'C'] }
Następnie należy zdecydować, jak będą przechowywane wartości dla krawędzi. Dla implementacji opartej o macierz sąsiedztwa wybór był oczywisty. W przypadku list mamy takie dwie możliwości: albo razem z indeksami krawędzi:
1edges_values = { 0: [(3,"green")], 1: [(2,"white"), (3,"red")],
2 2: [(1,"white")], 3:[(0,"green"), (2,"red")] }
albo w oddzielnych strukturach danych:
1edges = [ [3], [2, 3], [1], [0,2] ]
2e_val = [ ["green"], ["white", "red"], ["white"], ["green","red"] ]
W drugim przypadku implementacja metody get_edges(self, vi) jest bardzo łatwa, w przeciwieństwie do pierwszej opcji.
Pierwsza jednak gwarantuje, że danych na krawędziach jest tyle, ile krawędzi.
1class SimpleGraph:
2
3 def __init__(self):
4 self.__edges = []
5 self.__e_val = []
6 self.__vertc = []
7
8 def bind_vertices(self, vi, vj, edge):
9 self.bind_indexes(self.__index_for_vertex(vi), self.__index_for_vertex(vj), edge)
10
11 def bind_indexes(self, i, j, edge):
12
13 if self.are_connected(i, j): return
14 self.__edges[i].append(j)
15 self.__edges[j].append(i)
16 self.__e_val[i].append(edge)
17 self.__e_val[j].append(edge)
18
19 def are_connected(self, i, j):
20 return j in self.__edges[i]
21
22 def get_edge(self, vi, vj):
23 pass
24
25 def get_edges(self, vi):
26 return self.__e_val[self.__index_for_vertex(vi)]
27
28 def get_vertices(self, vi):
29 pass
30
31 def add_vertex(self, vi):
32 self.__vertc.append(vi)
33 self.__edges.append([])
34 self.__e_val.append([])
35 return len(self.__vertc) - 1
36
37 def __index_for_vertex(self, vi):
38 return self.__vertc.index(vi)
39
40if __name__ == "__main__":
41
42 g = SimpleGraph()
43 for ve in ['A','B','C','D','E'] :
44 g.add_vertex(ve)
45 g.bind_vertices('A','B',"blue")
46 g.bind_indexes(4,3, "red")