1.簡介
廣度優先算法(Breadth-First Search),同廣度優先搜索,又稱作寬度優先搜索,或橫向優先搜索,簡稱BFS,是一種圖形搜索演算法。簡單的說,BFS是從根節點開始,沿着樹的寬度遍歷樹的節點,如果發現目標,則演算終止。廣度優先搜索的實現一般採用open-closed表。
BFS是一種盲目搜尋法,目的是系統地展開並檢查圖中的所有節點,以找尋結果。換句話說,它並不考慮結果的可能位置,徹底地搜索整張圖,直到找到結果爲止。BFS並不使用經驗法則算法。
從算法的觀點,所有因爲展開節點而得到的子節點都會被加進一個先進先出的佇列中。一般的實作裏,其鄰居節點尚未被檢驗過的節點會被放置在一個被稱爲open的容器中(例如佇列或是鏈表),而被檢驗過的節點則被放置在被稱爲closed的容器中。(open-closed表)
2.應用
廣度優先搜索算法能用來解決圖論中的許多問題,例如:
尋找圖中所有連接元件(Connected Component)。一個連接元件是圖中的最大相連子圖。尋找連接元件中的所有節點。尋找非加權圖中任兩點的最短路徑。測試一圖是否爲二分圖。(Reverse) Cuthill–McKee算法
BFS 可用來解決電腦遊戲(例如即時策略遊戲)中找尋路徑的問題。在這個應用中,使用平面網格來代替圖形,而一個格子即是圖中的一個節點。所有節點都與它的鄰居(上、下、左、右、左上、右上、左下、右下)相接。
值得一提的是,當這樣使用BFS算法時,首先要先檢驗上、下、左、右的鄰居節點,再檢驗左上、右上、左下、右下的鄰居節點。這是因爲BFS趨向於先尋找斜向鄰居節點,而不是四方的鄰居節點,因此找到的路徑將不正確。BFS 應該先尋找四方鄰居節點,接着才尋找斜向鄰居節點1。
3.實現思路
廣度優先算法的實現方式爲: 將所有節點都遍歷一遍;
該算法的節點關係:如上圖所示;
由此我們可見,self(根節點)相鄰的節點(這裏我們稱爲客戶)有三個,分別爲: jack,rows,bob;
而jack的客戶有5個(加上根節點),分別爲: Awdrey,rows,Awdry,Ayckbourn,self;
以此類推......
我們的需求則是要在這些節點當中,找到芒果銷售商。(節點名字中包含m就爲芒果銷售商)
條件:已經判斷過的節點不能再次進行判斷 (每個客戶只需要判斷一次)
所以,這裏我們採用 廣度優先算法
廣度優先算法的實現方式爲: 將所有節點都遍歷一遍;
首先,我們從根節點(self) 爲中心,向其子節點(客戶)挨個進行遍歷,如果當前客戶中,沒有人是芒果銷售商的話,則將其客戶的客戶進行遍歷 (當前客戶必須要有客戶)類似於 一滴墨水滴入水中一點一點擴散的效果,以此實現對所有客戶的遍歷;
上圖中,jack是rows的客戶,同時rows又是jack的客戶,當算法遍歷到jack後,加入了jack的客戶...rows...,遍歷到jack的客戶rows,又加入了...jack..,此時該算法就會陷入遍歷死循環。
所以,我們需要一個記錄板(容器)記錄遍歷過的人。如果jack已經被遍歷並記錄了,那麼加入下一個客戶(節點)時,則會略過已經被記錄過的客戶(節點)
4.代碼實現
//定義集合,用於記錄遍歷過的person
private HashMap<String,Boolean> searched_map = new HashMap<>();
//隊列
String persons_queue = "";
public String search_seller(HashMap<String,String[]> search_arr,String search_person){
//當前需要查找的數組
String[] persons = search_arr.get(search_person);
if (persons != null && persons.length > 0){
for (String person : persons) {
persons_queue += person + ",";
}
while (persons_queue != null ){
//定義跳出循環條件
String all_person = "";
Set<String> strings = search_arr.keySet();
for (String string : strings) {
String[] strings1 = search_arr.get(string);
for (String s : strings1) {
all_person += s+",";
}
}
String[] split_person = persons_queue.split(",");
for (String person : split_person) {
//如果當前值沒有被遍歷過,則判斷當前值是否爲芒果銷售商(過濾遍歷過的person防止遍歷死循環)
if (!m_isExist(searched_map,person)){
//該條件可根據需求修改
if (person.contains("m")){
System.out.println(person + "是芒果銷售商.");
searched_map.put(person,true);
return person;
}else {
//反之 獲取當前person的值中的數組
String[] search= search_arr.get(person);
//當前數組不爲空,並且數組的長度要大於0
if (search != null && search.length >0) {
for (String per : search) {
//添加到需要遍歷的變量中 等待下次循環判斷其是否符合條件
persons_queue += per+",";
}
}
//添加當前數組的值進行標記,代表已經查詢過當前值了
searched_map.put(person,true);
System.out.println(person + "不是芒果銷售商.");
}
}
}
//跳出循環條件
if (split_person.length == all_person.split(",").length) {
break;
}
}
}
System.out.println("沒有人是芒果銷售商!");
return null;
}
//定義方法,判斷當前person是否已經被遍歷過了
public Boolean m_isExist(HashMap<String,Boolean> map,String person){
if (map.get(person) != null && map.size() >= 0) {
Boolean aBoolean = map.get(person);
return aBoolean;
}else {
return false;
}
}
public static void main(String[] args) {
// 節點名稱不可重複
HashMap<String,String[]> map = new HashMap<>();
String[] arr = {"jack","rows","bob"};
map.put("self",arr);
String[] arr2 = {"Awdrey","Awdry","Ayckbourn"};
map.put("jack",arr2);
String[] arr3 = {"Aylen","Aylward","Groves"};
map.put("rows",arr3);
String[] arr4 = {"das","dafa"};
map.put("Aylen",arr4);
String[] arr5 = {"oppo","si","daop"};
map.put("dafa",arr5);
BreadthFirstSearch breadthFirstSearch = new BreadthFirstSearch();
String message = breadthFirstSearch.search_seller(map, "self");
System.out.println(message);
}
打印結果如下:
注:示例代碼借鑑自《算法導論》第4版 (原代碼爲Python實現)
{\__/} {\__/}
( ·-·) (·-· )
/ >------------------------------------------------< \
| ☆ |
| ☆ |
| ★ |
| ☆ |
| ☆ |
| |
-------------------------------------
如有興趣可以關注我的公衆號: