圖是一種抽象的數學結構,研究抽象對象之間的一類二元關係及其拓撲性質。而在計算機的數據結構領域中,圖則是一種複雜的數據結構。node
一個圖是一個二元組 G = (V, E),其中:網絡
圖分爲無向圖和有向圖。在無向圖中邊沒有方向,用圓括號表示,例如(v1, v2)。在有向圖中邊是有方向的,用尖括號表示,例如<v1, v2>。數據結構
徹底圖:任意兩個頂點之間都有邊的圖。徹底圖有以下性質:app
對於圖 G=(V, E),若是存在頂點序列如 Vi0,Vi1,...,Vin,使得 (Vi0,Vi1)、(Vi1,Vi2)、...(Vin-1,Vin)都是圖的邊,則說從 Vi0 到 Vin 存在路徑,並稱 Vi0,Vi1,...,Vin 是從頂點 Vi0 到 Vin 的一條路徑。對於路徑,還有以下概念:spa
須要注意的是,從一個頂點到另外一個頂點,可能存在路徑,也可能不存在路徑。若是存在路徑,還可能存在多條路徑。code
1)連通對象
若是在一個無向圖中存在 vi 到 vj 的路徑,則稱這兩個頂點連通。blog
2)連通無向圖繼承
若是無向圖 G 中任意兩個頂點之間都連通,則稱 G 爲連通無向圖。遞歸
3)強連通有向圖
若是對於有向圖 G 中任意兩個頂點 vi 和 vj ,從 vi 到 vj 連通並從 vi 到 vj 也連通,則稱 G 爲強連通有向圖。
4)帶權圖
若是圖 G 中的每條邊都被賦予一個權值,則稱 G 爲帶權圖,能夠是有向圖也能夠是無向圖。邊的權值可用於表示實際應用中與頂點之間的關聯的有關信息,帶權的連通無向圖也被稱爲網絡。
圖做爲一種複雜的數據結構,能夠給圖定義以下操做:
ADT Graph:
Graph(self) # 用於建立一個圖
is_empty(self) # 判斷圖是否爲空
get_vertex(self) # 獲取頂點數量
get_edge(self) # 獲取邊的數量
add_edge(self, v1, v2) # 添加一條 v1 到 v2 的邊
has_edge(self, v1, v2) # 是否有 v1 到 v2 的邊
print(self) # 打印圖
圖的最基本表示方法是鄰接矩陣表示法。鄰接矩陣是表示圖中頂點間鄰接關係的矩陣,對於一個有 n 個頂點的圖 G=(V, E),其鄰接矩陣是一個 n*n 的矩陣,圖中每一個頂點對應於矩陣裏的一行和一列。
最簡單的鄰接矩陣是以0/1爲元素的,即對於圖 G 的鄰接矩陣,當 Aij 爲0時,表示頂點 vi 和 vj 無邊, 當 Aij 爲1時,表示頂點 vi 和 vj 有邊。鄰接矩陣表示了圖中的頂點數量和頂點間的關係(即邊),每一個頂點對應矩陣的一對行列下標,聽過一對下標就能夠肯定圖中的某條邊是否存在了。
要使用 Python 實現一個圖類,可使用列表(list)或者 numpy 模塊裏的矩陣(array),這裏就用列表來實現,即用 list 爲元素的 list,具體代碼以下:
1 # 使用鄰接矩陣 2 class Graph: 3 def __init__(self, v): 4 self.V = v 5 self.E = 0 6 self.data = [[0 for _ in range(v)] for _ in range(v)] 7 8 def is_empty(self): 9 """ 10 判斷圖是否爲空 11 :return: 12 """ 13 return self.V == 0 14 15 def get_vertex(self): 16 """ 17 獲取頂點數量 18 :return: 19 """ 20 return self.V 21 22 def get_edge(self): 23 """ 24 獲取邊的數量 25 :return: 26 """ 27 return self.E 28 29 def add_edge(self, v1, v2): 30 """ 31 向圖中增長一條v1到v2的邊 32 :param v1: 頂點 33 :param v2: 頂點 34 :return: 35 """ 36 if not self.data[v1][v2]: 37 self.data[v1][v2] = 1 38 self.data[v2][v1] = 1 39 self.E += 1 40 41 def has_edge(self, v1, v2): 42 """ 43 判斷是否有v1到v2的邊 44 :param v1: 頂點 45 :param v2: 頂點 46 :return: 47 """ 48 return self.data[v1][v2] == 1 49 50 def print(self): 51 """ 52 將圖打印出來 53 :return: 54 """ 55 for i in self.data: 56 print(i)
用鄰接矩陣表示也有必定缺點,由於圖的鄰接矩陣常常是稀疏的,即圖中大部分元素表示的都是無邊的值,爲了下降圖表示的空間代價,可使用鄰接表表示法。
所謂鄰接表,就是爲圖中每一個頂點關聯一個表,其中記錄的是這個頂點的全部鄰接邊。頂點是圖中最基本的部分,一般有標識,也能夠順序編號,以即可以經過編號隨機訪問。
要用鄰接表來表示圖,這裏能夠繼承前面的類 Graph,須要對初始化方法、增長邊等方法進行修改,具體代碼以下:
1 # 使用鄰接表 2 class GraphAL(Graph): 3 def __init__(self, v): 4 super(GraphAL, self).__init__(v) 5 self.V = v 6 self.E = 0 7 self.data = [[] for _ in range(v)] 8 9 def add_edge(self, v1, v2): 10 """ 11 向圖中增長一條v1到v2的邊 12 :param v1: 頂點 13 :param v2: 頂點 14 :return: 15 """ 16 if v2 not in self.data[v1]: 17 self.data[v1].append(v2) 18 self.data[v2].append(v1) 19 self.E += 1 20 21 def has_edge(self, v1, v2): 22 """ 23 判斷是否有v1到v2的邊 24 :param v1: 頂點 25 :param v2: 頂點 26 :return: 27 """ 28 return v2 in self.data[v1]
按照深度優先搜索(Depth-First Search)的方法遍歷整個圖,假設從頂點 v 出發進行搜索,深度優先搜索的作法是:
按照廣度優先搜索(Breadth-First Search)的方法遍歷整個圖,假設從頂點 v 出發進行搜索,廣度優先搜索的作法是:
前面已經說過,深度優先搜索中包含了一個重複調用的過程,於是可使用遞歸來實現深度優先搜索對圖進行遍歷。
在遍歷的過程當中,須要對已經訪問過的頂點進行標記,因此定義了一個列表 visited,訪問過的頂點就置爲 True。除此以外,還要對訪問的頂點進行記錄,能夠用一個列表來保存,定義爲 path。具體代碼以下:
1 def graph_dfs(graph: GraphAL, s: int): 2 """ 3 運用深度優先搜索對圖進行遍歷 4 :param graph: 圖 5 :param s: 起點 6 :return: 7 """ 8 global visited, path, start 9 visited = [False] * graph.get_vertex() 10 path = [0] * graph.get_vertex() 11 start = s 12 dfs(graph, s) 13 14 15 def dfs(graph: GraphAL, v: int): 16 """ 17 深度優先搜索 18 :param graph: 圖 19 :param v: 頂點 20 :return: 21 """ 22 visited[v] = True 23 for i in graph.get_adj(v): 24 if not visited[i]: 25 path[i] = v 26 dfs(graph, i)
在使用廣度優先搜索對圖進行遍歷的過程當中,須要使用到隊列,首先將起點入隊,當隊列非空時,重複執行如下過程:
當隊列爲空時,代表搜索完成,結束遍歷。具體代碼以下:
1 def graph_bfs(graph: GraphAL, s: int): 2 """ 3 運用廣度優先搜索對圖進行遍歷 4 :param graph: 圖 5 :param s: 起點 6 :return: 7 """ 8 global visited, path, start 9 visited = [False] * graph.get_vertex() 10 path = [0] * graph.get_vertex() 11 start = s 12 bfs(graph, s) 13 14 15 def bfs(graph: GraphAL, v: int): 16 """ 17 廣度優先搜索 18 :param graph: 圖 19 :param v: 頂點 20 :return: 21 """ 22 visited[v] = True 23 q = Queue() 24 q.put(v) 25 while not q.empty(): 26 v = q.get() 27 for node in graph.get_adj(v): 28 if not visited[node]: 29 path[node] = v 30 visited[node] = True 31 q.put(node)
建立一個無向圖 G,以下:
使用鄰接表來表示這個圖,調用以前定義的圖類 GraphAL,再將每條邊依次添加進去,代碼以下:
1 graph_test = GraphAL(10) 2 graph_test.add_edge(0, 1) 3 graph_test.add_edge(1, 2) 4 graph_test.add_edge(1, 6) 5 graph_test.add_edge(2, 3) 6 graph_test.add_edge(2, 7) 7 graph_test.add_edge(3, 4) 8 graph_test.add_edge(3, 8) 9 graph_test.add_edge(5, 6) 10 graph_test.add_edge(7, 8) 11 graph_test.add_edge(8, 9) 12 graph_test.print()
前面搜索過程結束以後,路徑都保存在了列表 path 之中,若咱們想要獲取從起點到目標頂點的路徑,就須要從 path 中進行獲取,獲取路徑的方法的代碼以下:
1 def get_path_to(v: int): 2 """ 3 獲取從頂點start到頂點v的路徑 4 :param v: 目標頂點 5 :return: 6 """ 7 x = path[v] 8 result = [v, x] 9 while x != start: 10 x = path[x] 11 result.append(x) 12 print(result[::-1])
最後就是分別使用深度優先搜索和廣度優先搜索對圖進行遍歷,起點爲頂點1,目標頂點爲頂點8,最終分別使用 get_path_to() 獲取路徑,代碼以下:
1 visited = [] 2 path = [] 3 start = None 4 graph_dfs(graph_test, 0) 5 get_path_to(7) 6 # [0, 1, 2, 3, 8, 7] 7 graph_bfs(graph_test, 0) 8 get_path_to(7) 9 # [0, 1, 2, 7]