圖表示的是多點之間的鏈接關係,由節點和邊組成。類型分爲有向圖,無向圖,加權圖等,任何問題只要能抽象爲圖,那麼就能夠應用相應的圖算法。node
這裏咱們以有向圖舉例,有向圖的鄰居節點是要順着箭頭方向,逆箭頭方向的節點不算做鄰居節點。 在python中,咱們使用字典來表示圖,咱們將圖相鄰節點之間的鏈接轉換爲字典鍵值之間的映射關係。好比上圖中的1的相鄰節點爲2和3,便可表示以下:python
graph={} graph[1] = [2,3]
按照這種方式,上圖能夠完整表示爲:算法
graph={} graph[1] = [3,2] # 這裏爲了演示,調換一下位置 graph[2] = [5] graph[3] = [4,7] graph[4] = [6] graph[5] = [6] graph[6] = [8] graph[7] = [8] graph[8] = []
如此咱們將全部節點和其相鄰節點之間的鏈接關係所有描述一遍就獲得了圖的字典表示形式。節點8因爲沒有相鄰節點,咱們將其置爲空列表。app
廣度優先搜索和深度優先搜索是圖遍歷的兩種算法,廣度和深度的區別在於對節點的遍歷順序不一樣。廣度優先算法的遍歷順序是由近及遠,先看到的節點先遍歷。 接下來使用python實現廣度優先搜索並找到最短路徑:spa
from collections import deque from collections import namedtuple def bfs(start_node, end_node, graph): # 開始節點 目標節點 圖字典 node = namedtuple('node', 'name, from_node') # 使用namedtuple定義節點,用於存儲前置節點 search_queue = deque() # 使用雙端隊列,這裏看成隊列使用,根據先進先出獲取下一個遍歷的節點 name_search = deque() # 存儲隊列中已有的節點名稱 visited = {} # 存儲已經訪問過的節點 search_queue.append(node(start_node, None)) # 填入初始節點,從隊列後面加入 name_search.append(start_node) # 填入初始節點名稱 path = [] # 用戶回溯路徑 path_len = 0 # 路徑長度 print('開始搜索...') while search_queue: # 只要搜索隊列中有數據就一直遍歷下去 print('待遍歷節點: ', name_search) current_node = search_queue.popleft() # 從隊列前邊獲取節點,即先進先出,這是BFS的核心 name_search.popleft() # 將名稱也相應彈出 if current_node.name not in visited: # 當前節點是否被訪問過 print('當前節點: ', current_node.name, end=' | ') if current_node.name == end_node: # 退出條件,找到了目標節點,接下來執行路徑回溯和長度計算 pre_node = current_node # 路徑回溯的關鍵在於每一個節點中存儲的前置節點 while True: # 開啓循環直到找到開始節點 if pre_node.name == start_node: # 退出條件:前置節點爲開始節點 path.append(start_node) # 退出前將開始節點也加入路徑,保證路徑的完整性 break else: path.append(pre_node.name) # 不斷將前置節點名稱加入路徑 pre_node = visited[pre_node.from_node] # 取出前置節點的前置節點,依次類推 path_len = len(path) - 1 # 得到完整路徑後,長度即爲節點個數-1 break else: visited[current_node.name] = current_node # 若是沒有找到目標節點,將節點設爲已訪問,並將相鄰節點加入搜索隊列,繼續找下去 for node_name in graph[current_node.name]: # 遍歷相鄰節點,判斷相鄰節點是否已經在搜索隊列 if node_name not in name_search: # 若是相鄰節點不在搜索隊列則進行添加 search_queue.append(node(node_name, current_node.name)) name_search.append(node_name) print('搜索完畢,最短路徑爲:', path[::-1], "長度爲:", path_len) # 打印搜索結果 if __name__ == "__main__": graph = dict() # 使用字典表示有向圖 graph[1] = [3, 2] graph[2] = [5] graph[3] = [4, 7] graph[4] = [6] graph[5] = [6] graph[6] = [8] graph[7] = [8] graph[8] = [] bfs(1, 8, graph) # 執行搜索
搜索結果code
開始搜索... 待遍歷節點: deque([1]) 當前節點: 1 | 待遍歷節點: deque([3, 2]) 當前節點: 3 | 待遍歷節點: deque([2, 4, 7]) 當前節點: 2 | 待遍歷節點: deque([4, 7, 5]) 當前節點: 4 | 待遍歷節點: deque([7, 5, 6]) 當前節點: 7 | 待遍歷節點: deque([5, 6, 8]) 當前節點: 5 | 待遍歷節點: deque([6, 8]) 當前節點: 6 | 待遍歷節點: deque([8]) 當前節點: 8 | 搜索完畢,最短路徑爲: [1, 3, 7, 8] 長度爲: 3
廣度優先搜索的適用場景:只適用於深度不深且權值相同的圖,搜索的結果爲最短路徑或者最小權值和。blog
深度優先搜索的遍歷順序爲一條路徑走到底而後回溯再走下一條路徑,這種遍歷方法很省內存可是不能一次性給出最短路徑或者最優解。 用python實現深度優先算法只須要在廣度的基礎上將搜索隊列改成搜索棧便可:隊列
from collections import deque from collections import namedtuple def bfs(start_node, end_node, graph): node = namedtuple('node', 'name, from_node') search_stack = deque() # 這裏看成棧使用 name_search = deque() visited = {} search_stack.append(node(start_node, None)) name_search.append(start_node) path = [] path_len = 0 print('開始搜索...') while search_stack: print('待遍歷節點: ', name_search) current_node = search_stack.pop() # 使用棧模式,即後進先出,這是DFS的核心 name_search.pop() if current_node.name not in visited: print('當前節點: ', current_node.name, end=' | ') if current_node.name == end_node: pre_node = current_node while True: if pre_node.name == start_node: path.append(start_node) break else: path.append(pre_node.name) pre_node = visited[pre_node.from_node] path_len = len(path) - 1 break else: visited[current_node.name] = current_node for node_name in graph[current_node.name]: if node_name not in name_search: search_stack.append(node(node_name, current_node.name)) name_search.append(node_name) print('搜索完畢,路徑爲:', path[::-1], "長度爲:", path_len) # 這裏再也不是最短路徑,深度優先搜索沒法一次給出最短路徑 if __name__ == "__main__": graph = dict() graph[1] = [3, 2] graph[2] = [5] graph[3] = [4, 7] graph[4] = [6] graph[5] = [6] graph[6] = [8] graph[7] = [8] graph[8] = [] bfs(1, 8, graph)
搜索結果內存
開始搜索... 待遍歷節點: deque([1]) 當前節點: 1 | 待遍歷節點: deque([3, 2]) 當前節點: 2 | 待遍歷節點: deque([3, 5]) 當前節點: 5 | 待遍歷節點: deque([3, 6]) 當前節點: 6 | 待遍歷節點: deque([3, 8]) 當前節點: 8 | 搜索完畢,路徑爲: [1, 2, 5, 6, 8] 長度爲: 4
python的deque根據pop仍是popleft能夠當成棧或隊列使用,DFS的可以很快給出解,但不必定是最優解。 深度優先搜索的適用場景: 針對深度很深或者深度不肯定的圖或者權值不相同的圖能夠適用DFS,優點在於節省資源,但想要獲得最優解須要完整遍歷後比對全部路徑選取最優解。資源