广度优先搜索

  • 《算法图解》学习笔记

图是什么?

图模拟一组连接,用于模拟不同的东西是如何相连的。例如:



图由节点和边组成。一个节点可能与众多节点直接相连,这些节点被称为邻居。



有向图(directed graph)的关系是单向的,无向图(undirected graph)没有箭头,直接相连的节点互为邻居。例如,下面两个图是等价的。

广度优先搜索

广度优先搜索是一种用于图的查找算法,可帮助回答两类问题。

  • 第一类问题:从节点A出发,有前往节点B的路径吗?
  • 第二类问题:从节点A出发,前往节点B的哪条路径最短?

我们来看下面的一个图

寻找芒果销售商

现在假设你要寻找一个芒果经销商,你寻找的途径一般是询问自己的朋友,如果朋友中没有芒果经销商,就可以拜托朋友去询问他的朋友,在图中我们可以看到,你自己的朋友是一度关系,也就是比较亲密,如果自己的朋友就是经销商,何必再去二度关系——朋友的朋友那里寻找呢?这就是广度优先搜索,在广度优先搜索的执行过程中,搜索范围从起点开始逐渐向外延伸,即先检查一度关系,再检查二度关系。

你按顺序依次检查名单中的每个人,看看他是否是芒果销售商。这将先在一度关系中查找,再在二度关系中查找,因此找到的是关系最近的芒果销售商。广度优先搜索不仅查找从A到B的路径,而且找到的是最短的路径。


代码实现
图由多个节点组成。每个节点都与邻近节点相连,如果表示类似于“你→Bob”这样的关系呢?我们可以使用散列表,散列表让你能够将键映射到值。在这里,你要将节点映射到其所有邻居。
你与你的邻居,“你”为一个键,“邻居”为值

表示这种映射关系的Python代码如下。

graph = {} # 建立一个空字典
graph["you"] = ["alice", "bob", "claire"] # 你对应你的邻居们

那么全部的图,代码可以这样表示了(节点为键或值,边则对应着键值的关系)

graph = {} 
graph["you"] = ["alice", "bob", "claire"] 
graph["bob"] = ["anuj", "peggy"]
graph["alice"] = ["peggy"]
graph["claire"] = ["thom", "jonny"]
graph["anuj"] = []    
graph["peggy"] = []    
graph["thom"] = []    
graph["jonny"] = []    

接下来我们使用队列这个数据结构,按序查找,首先询问一度关系的朋友,如果该朋友不是经销商,那么把该朋友的朋友再加入到队列中来,依次类推,完成问询。如果队列空了,就表示你的朋友关系网中没有芒果经销商。

如果遇到关系是双向的,或者一个朋友即使你的朋友也是你朋友的朋友,就会产生一些问题,会将同一个人查询两次或多次,导致无限循环。于是,我们需要创建一个列表,存储已经询问过的人,当他再次在队列中出现时,不必询问。

最终示例代码如下

from collections import deque

graph = {} 
graph["you"] = ["alice", "bob", "claire"] 
graph["bob"] = ["anuj", "peggy"]
graph["alice"] = ["peggy"]
graph["claire"] = ["thom", "jonny"]
graph["anuj"] = []    
graph["peggy"] = []    
graph["thom"] = []    
graph["jonny"] = [] 

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_seler(person): # 判断是否为经销商
                print(person+"is a mango seller")
                return True
            else:
                search_queue += graph[person] # 该朋友不是经销商,遂将该朋友的朋友加入队列
                searched.append(person) # 将该朋友记录到已询问名单
    return False

search("you")
运行时间

如果你在你的整个人际关系网中搜索芒果销售商,就意味着你将沿每条边前行(记住,边是从一个人到另一个人的箭头或连接),因此运行时间至少为O(边数)。

你还使用了一个队列,其中包含要检查的每个人。将一个人添加到队列需要的时间是固定的,即为O(1),因此对每个人都这样做需要的总时间为O(人数)。所以,广度优先搜索的运行时间为O(人数+边数),这通常写作O(V+E),其中V为顶点(vertice)数,E为边数。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

友情链接更多精彩内容