小白的算法初识课堂(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 Trueelse:search_queue += graph[person]searched.append(person)return Falsesearch('me')控制臺輸出:
I find you Ada!我們看到,我們在上面的python代碼中加入了一個已搜索名單searched,這是為了防止循環和低效率。比如,Huang和Bai都有一個朋友Tim,但是我們只需要檢查一次Tim,否則重復查詢就是做了無用功。
并且如果出現下面這種情況,我們就會進入死循環:
所以,在檢查一個人是否是編輯之前,確認此人是否被檢查過,就十分重要了。
運行時間
如果我在我的關系網中搜尋編輯,那就意味著我沿著每條邊前行,因此運行時間至少是O(邊數)。這里,我們還使用了一個隊列,其中包含要檢查的每個人。將一個人添加到隊列需要的時間是固定的,即為O(1),因此對每個人都這樣做需要的總時間為O(人數)。所以,廣度優先搜索的運行時間為O(人數 + 邊數),這通常寫作O(V + E),其中V 為頂點數,E 為邊數。
總結
以上是生活随笔為你收集整理的小白的算法初识课堂(part6)--广度优先搜索的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 男童酒店客房误食用过的安全套 家长与酒店
- 下一篇: 华为高管亲自劝告:千万别给折叠屏手机贴屏