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","rows","Awdry","Ayckbourn"};
map.put("jack",arr2);
String[] arr3 = {"jack","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实现)
如有兴趣可以关注我的公众号: