小白的算法初識課堂(part6)--廣度優先搜索

學習筆記
學習書目:《算法圖解》- Aditya Bhargava




圖簡介


今天是五一,假如我要從家出發去公園玩,現在可去公園的公交車路線如下:

在這裏插入圖片描述


現在,我想找一條換乘最少的線路,該使用什麼樣的算法呢?

我們先找出一步就能到達的地方,顯而易見,一步能到達A、B;再找出兩步能到達的地方,經過簡單尋找,我們發現兩步能到達E、D、C三個地;第三步呢?可以看出第三步可以到達公園和E。很好!此時我們就找到了換乘最少的路線:家–1-->路–>A–3路–>E–5路–>公園

這種問題被稱爲最短路徑問題。我們要找出最短路徑,這可能是前往朋友家的最短路徑,也可能是國際象棋中把對方將死的最少步數。解決最短路徑問題的算法被稱爲廣度優先搜索。

我們解決這個問題時,用了兩個步驟:

(1)使用圖來建立問題模型

(2)使用廣度優先搜索解決問題


圖是啥


圖模擬了一組連接,比如可像下圖一樣表示小黃欠我錢:

在這裏插入圖片描述

我們再看一幅更復雜的圖:

在這裏插入圖片描述


可以看到這幅圖由節點組成,一個節點可能與衆多節點直接相連,這些節點被稱爲鄰居。比如,我是小黃的鄰居,但奶奶不是小黃的鄰居;奶奶既是我的鄰居,又是大白的鄰居;但是,奶奶沒有鄰居,因爲雖然有指向她的箭頭,卻沒有從她出發的箭頭。這種圖叫做有向圖,其中的關係是單向的。無向圖則沒有箭頭,直接相連的節點互爲鄰居。


我們看到,下面兩個圖是等價的:

在這裏插入圖片描述


廣度優先搜索


廣度優先搜索是一種用於圖的查找算法,可幫助回答兩類問題。

第一類問題:從節點A出發,有前往節點B的路徑嗎?

第二類問題:從節點A出發,前往節點B的哪條路徑最短?

假如我是一位作者,我想在我的Twitter的朋友列表裏找一位編輯。我的想法很簡單,先在朋友裏找有沒有編輯,沒有的話就在朋友的朋友裏尋找,再沒有的話,就在朋友的朋友的朋友裏尋找…以此類推


現在我有3個朋友:

在這裏插入圖片描述

我先創建一個朋友名單:

['Huang', 'Hei', 'Bai']

然後依次檢查我的3個朋友是否是編輯。假如我沒有朋友是編輯,那麼我就必須在朋友的朋友中尋找:

在這裏插入圖片描述


比如,我發現Huang不是編輯,那我就把它的朋友Write和Tim加入我的朋友名單,並把Huang從名單中剔除:

['Hei', 'Bai', 'Write', 'Tim']

使用這種算法將搜遍我的整個人際關係網,直到找到編輯。這就是廣度優先搜索算法。


尋找最短路徑


由我在Twitter的朋友列表裏找編輯的例子中,我們已經回答了廣度優先搜索的第一個問題(從節點A出發,有前往節點B的路徑嗎?)現在,我們就要回答第二個問題,即哪位編輯是離我關係最近。比如,我的朋友和我是一度關係,我朋友的朋友和我是二度關係。在我看來,一度關係勝過二度關係。因此,我要現在一度關係中尋找編輯,沒有的話,再從二度關係中尋找編輯,以此類推。

需要注意的是,我們必須把一度關係查找完,才能查找二度關係。比如,我必須先查找完Huang, Hei, Bai才能查找Write等二度關係。

以我們剛剛建立的朋友名單爲例,一度關係在二度關係之前加入名單:

['Hei', 'Bai',' Write', 'Tim']

我們按順序依次檢查名單中的每個人,看看他是否是編輯。這將先在一度關係中查找,再在二度關係中查找,因此找到的是關係最近的編輯。

注意!只有按順序查找才能找到與我關係最近的編輯。換句話說Bai先於Tim加入名單,就要先檢查Bai。有一個可實現這種目的的數據結構,那就是隊列


隊列


隊列類似於棧,你不能隨機地訪問隊列中的元素。隊列只支持兩種操作:入隊和出隊。如果我將A和B加入隊列,先加入隊列的A也將先出隊,而後加入的B則會後出隊。

在這裏插入圖片描述

隊列是一種先進先出(First In First Out,FIFO)的數據結構,而棧是一種後進先出(Last In First Out,LIFO)的數據結構。


實現圖


現在,我們將用散列表來表達這種我-->Huang的關係,並用python代碼來實現圖:

graph = {}
graph['me'] = ['Huang', 'Hei', 'Bai']
graph['Huang'] = ['Write', 'Tim']
graph['Bai'] = ['Tim']
graph['Hei'] = ['Black', 'Ada']
graph['Write'] = []
graph['Tim'] = []
graph['Black'] = []
graph['Ada'] = []

我們看到Write、Tim、Black、Ada沒有鄰居,因爲只有指向它們的箭頭,卻沒有從它們出發的箭頭。


實現算法


python代碼:

from collections import deque

#判斷誰是編輯
def person_is_edit(name):
    return name[-1] == 'a'
    #假設名字最後一個字母是a就是編輯

#查找某人的關係列表中誰是編輯
def search(name):
    search_queue = deque()
    search_queue += graph[name]
    searched = []
    #記錄已經檢查過的人,防止低效率和無限循環
    
    while search_queue:
        person = search_queue.popleft()
        if person not in searched:
            
            if person_is_edit(person):
                print('I find you {}!'.format(person))
                return True
            else:
                search_queue += graph[person]
            searched.append(person)
    return False

search('me')

控制檯輸出:

I find you Ada!

我們看到,我們在上面的python代碼中加入了一個已搜索名單searched,這是爲了防止循環和低效率。比如,Huang和Bai都有一個朋友Tim,但是我們只需要檢查一次Tim,否則重複查詢就是做了無用功。

並且如果出現下面這種情況,我們就會進入死循環:

在這裏插入圖片描述

所以,在檢查一個人是否是編輯之前,確認此人是否被檢查過,就十分重要了。


運行時間


如果我在我的關係網中搜尋編輯,那就意味着我沿着每條邊前行,因此運行時間至少是O(邊數)。這裏,我們還使用了一個隊列,其中包含要檢查的每個人。將一個人添加到隊列需要的時間是固定的,即爲O(1),因此對每個人都這樣做需要的總時間爲O(人數)。所以,廣度優先搜索的運行時間爲O(人數 + 邊數),這通常寫作O(V + E),其中V 爲頂點數,E 爲邊數。

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