每天学习一点儿算法--广度优先搜索
广度优先搜索(BFS)是我们学的第一种图算法,它可以让你找出两样东西之间的最短距离。
这里提到了一个新的概念:图, 那什么是图呢?
图简介
图用于模拟不同的东西是如何相连的:
图由节点(node)和边(edge)组成。一个节点可以与众多的节点直接相连。
再来看这个图:
从1到5的最短路径是怎样的呢?由于节点比较少,我们一眼就可看出这条路径是最短的:
其实这就是一个广度优先搜索的例子。解决最短路径问题的算法称之为广度优先搜索。
解决这种最短路径问题需要两个步骤:
- 使用图来建立问题模型
- 使用广度优先搜索来解决问题
广度优先搜索
到目前为止,我们已经学过简单查找、二分查找和散列表三种查找算法。广度优先搜索也是一种查找算法,它是一种用于图的查找算法。
广度优先搜索可用于解决两类问题:
- 第一类问题:从节点A出发,有前往节点B的路径么?
- 第二类问题:从节点A出发,前往节点B的哪条路径最短?
譬如下面这个例子:找出与你关系最近的胖子。在这里,朋友是一度关系,朋友的朋友是二度关系。
在你看来,一度关系胜过二度关系,二度关系胜过三度关系。以此类推,因此,我们应该先在一度关系里面搜索是否有胖子,再在二度关系里面搜索是否有胖子。这就是广度优先搜索的原理。
广度优先搜索不仅查找从A到B的路径,而且找到的是最短路径。注意,只有按照添加顺序查找时,才能实现这样的目的。这里就需要用到一种名为队列(queue)的数据结构。
队列类似于栈,只支持两种基本操作:入队和出队。队列是一种先进先出(FIFO)的数据结构;而栈是一种后进先出(LIFO)的数据结构。
实现图
下面我们用Python代码来实现图吧。图由多个节点组成,各个节点之间的联系可以看成是一种映射,于是我们可以使用散列表来实现这种关系:
表示这种映射关系的Python代码如下:
graph = {}
graph["you"] = ["alice", "bob", "claire"]
这里“你”被映射到了一个数组,因此 graph["you"]
是一个数组,其中包含了“你”的所有朋友。
图就是一系列的节点和边,譬如像下面这样更大的图:
表示它的Python代码如下:
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"] = []
提示:散列表是无序的,因此添加键-值对顺序无关紧要。
有箭头的图称为有向图,其中的关系是单向的;无箭头的图称为无向图,其中的关系是双向的。例如,下面两个图是等价的:
实现算法
先概述一下这种算法的工作原理:
首先,创建一个队列。在Python中,可使用函数deque来创建一个双端队列。
from collections import deque
search_queue = deque() # 创建一个队列
search_queue += graph["you"] # 将你的朋友加入到搜索队列中
下面来看看其他的代码:
def person_is_pangzi(name):
"""检查这个人是否是胖子"""
return name[-1] == 'y' # 如果名字以y结尾就是胖子,哈哈~~,好奇葩的判断
while search_queue: # 只要队列不为空
person = search_queue.popleft() # 就取出其中的一个人
if person_is_pangzi(person): # 检查这个人是不是胖子
print(person + " is a 胖子! ")
else:
search_queue += graph[person] # 不是胖子,就将它的朋友加入到队列中
考虑到不能重复检查一个人,否则有可能陷入死循环。因此,最终代码如下:
from collections import deque
def person_is_pangzi(name):
"""检查这个人是否是胖子"""
return name[-1] == 'y' # 如果名字以y结尾就是胖子,哈哈~~,好奇葩的判断
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_pangzi(person): # 检查这个人是不是胖子
print(person + " is a 胖子! ")
else:
search_queue += graph[person] # 不是胖子,就将它的朋友加入到队列中
searched.append(person) # 将他添加到已检查列表中
search("you")
广度优先搜索的运行时间为O(V+E), 其中V为顶点数, E为边数。
小结
- 广度优先搜索用于解决最短路径问题
- 带箭头的为有向图,其中的关系是单向的
- 不带箭头的为无向图,其中的关系是双向的
- 队列是先进先出的结构;栈是后进先出的结构
每天学习一点点,每天进步一点点。
- 微信小程序重磅功能上线!一键连Wi-Fi/手机变门禁卡
- MySQL下载安装、基本配置、问题处理
- windows下命令行模式中cd命令无效的原因
- 分布式和集群区别?什么是云计算平台?分布式的应用场景?
- 中国移动也要搞自动驾驶,没了SIM卡怎么耍花样?
- python并发编程之多进程理论部分
- 使用concurrent.futures模块并发,实现进程池、线程池
- 人工智能与医疗
- 每周论文清单:知识图谱,文本匹配,图像翻译,视频对象分割
- 进程池、线程池、回调函数
- java学习:weblogic下JNDI及JDBC连接测试(weblogic环境)
- 简单谈谈python的反射机制
- java学习:使用dom4j读写xml文件
- python文件和目录操作方法大全
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法