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