Python廣度優先查找和深度優先查找(1) 圖基本認識 廣度優先搜索(breadth-first search, BFS) 深度優先查找

圖,特別是圖的ADT(抽象數據類型)在計算機科學和數學領域是應用非常廣泛的。

圖基本認識

圖模擬一組連接,假如你在A點,你到B點的路徑,可以使用圖來代表。


圖由圖由頂點(vertex,node)和邊(edge)組成。一個頂點(vertex)可以與多個頂點連接,通過一條線,這條線稱爲邊(edge)。

圖用於模擬一組數據是如何連接的

在Python中使用字典來實現這種連接關係。在之前的文章,我已經介紹了Python字典的實現原理。

廣度優先搜索(breadth-first search, BFS)

首先,BFS可以解決兩種問題

    1. 從節點A出發,能不能到節點X?
    1. 從節點A出發,到節點X的最短路徑是哪條?

如何理解廣度:
假如現在你需要在你的朋友的關係網中,找到一個賣芒果的,應該如何做?下圖是你的朋友關係網的情況。

廣度優先,就是, 在你的關係網中,一度關係勝過二度關係。廣度優先,就是先在一度關係中找到,再往外層的度找。

現在我們用BFS來解決第一個問題。

這裏參考了《算法圖解》的例子。

先把上圖寫爲對應的Python代碼:

graph = dict()
graph['your'] = ['alice', 'boob', 'claire']
graph['boob'] = ['anuj', 'peggy']
graph['alice'] = ['peggy']
graph['claire'] = ['thom', 'jonny']
graph['anuj'] = []
graph['peggy'] = []
graph['thom'] = []
graph['jonny'] = []

我們使用隊列(FIFO)的思想,將關係網的人加入到隊列,然後彈出一個元素,並且把該元素的關係網的朋友也加入到隊列。下面是代碼實現:

def person_is_seller(name):
    return name[-1] == 'm'


def search(graph, name):
    search_queue = deque()
    search_queue += graph[name]
    searched = []

    while search_queue:
        person = search_queue.popleft()
        if person not in search_queue:
            if person_is_seller(person):
                print(searched)
                print(person + ' is a mango seller')
                return True
            else:
                search_queue += graph[person]
                searched.append(person)
    return False

這樣的代碼只是告訴了我們,在關係網中能夠找到那個人(以m字母結尾),並且可以得到那個人的信息。
上面的代碼還是比較簡單的,現在我們來解決第二個問題,最短路徑是哪個?也就是,如何把走過的路徑保存起來?

def bfs2(graph, start):
    queue = [(start, [start])]  #  ['your', ['your']]
    while queue:
        (vertex, path) = queue.pop(0)
        for next_friend in graph[vertex]:
            if person_is_seller(next_friend):
                yield path + [next_friend]
            else:
                queue.append((next_friend, path + [next_friend]))

我們使用列表模擬隊列也是一樣的, 你也可以使用列表來代替yield。這裏其實還有一個問題,就是可能會重複計算。因爲並沒有像之前那樣,把查找過的路徑進行判斷。由於我們的graph的數據只是單指向的,所以也並不會有什麼問題,並且,這樣更容易理解一些。下面解釋了上面的代碼做了什麼:

  • 在queue裏面存儲每一個人(在圖論中稱爲頂點,vertex),以及它的路徑,用一個元組包裹。
  • 循環這個queue,每次只彈出最先進去的元素,得到一個元組(頂點,路徑)
  • 根據元組的頂點得到graph的數據(朋友的朋友),遍歷這些朋友
  • 如果不是要找的芒果商,那麼把該朋友也添加到隊列裏面去,同時把路徑也保存。

現在,在if判斷加===,在else加打印queue,再給jonny添加一位朋友

graph['jonny'] = ['abcem']
[('alice', ['your', 'alice'])]
[('alice', ['your', 'alice']), ('boob', ['your', 'boob'])]
[('alice', ['your', 'alice']), ('boob', ['your', 'boob']), ('claire', ['your', 'claire'])]
[('boob', ['your', 'boob']), ('claire', ['your', 'claire']), ('peggy', ['your', 'alice', 'peggy'])]
[('claire', ['your', 'claire']), ('peggy', ['your', 'alice', 'peggy']), ('anuj', ['your', 'boob', 'anuj'])]
[('claire', ['your', 'claire']), ('peggy', ['your', 'alice', 'peggy']), ('anuj', ['your', 'boob', 'anuj']), ('peggy', ['your', 'boob', 'peggy'])]
===
[('peggy', ['your', 'alice', 'peggy']), ('anuj', ['your', 'boob', 'anuj']), ('peggy', ['your', 'boob', 'peggy']), ('jonny', ['your', 'claire', 'jonny'])]
===
[['your', 'claire', 'thom'], ['your', 'claire', 'jonny', 'abcem']]

可以看到,最近一度的朋友總是會被先探測到。這就是廣度優先查找。

到此,基本的廣度優先查找算法已經實現。

深度優先查找

深度優先查找,只要把隊列變成棧即可。

def dfs(graph, start):
    queue = [(start, [start])]  # 1: ['A', ['A']]
    paths = []
    while queue:
        (vertex, path) = queue.pop()
        for next_friend in graph[vertex]:
            if person_is_seller(next_friend):
                print('===')
                paths.append(path + [next_friend])
            else:
                queue.append((next_friend, path + [next_friend]))
                print(queue)
    return paths
  • 第一次, 會把your的三個朋友壓入棧
  • 彈出後入棧的朋友,找到他的朋友,再添加入棧,以此類推,每次出棧都會是最新添加的

由於每次出棧的都是新的數據,所以會深入一條一條朋友網的線找下去。這就是深度優先。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章