算法图解——读书笔记06

广度优先搜索

首先,先介绍一下什么是图(它们不涉及X轴和Y轴),再介绍第一种图算法——广度优先搜素(breadth-frist search,BFS)。
广度优先搜素让你能够找出两样东西之间的最短距离,不过最短距离的含义有很多!使用广度优先搜索可以:
❑ 编写国际跳棋AI,计算最少走多少步就可获胜;
❑ 编写拼写检查器,计算最少编辑多少个地方就可将错拼的单词改成正确的单词,如将READED改为READER需要编辑一个地方;
❑ 根据你的人际关系网络找到关系最近的医生。

图简介
图是什么,图模拟一组连接。假如你与朋友玩牌,并要模拟谁欠谁钱,可像下面这样指出Alex欠Rama钱。
在这里插入图片描述
完整的欠钱图可能类似于下面这样。
在这里插入图片描述
Alex欠Rama钱,Tom欠Adit钱,等等。图由节点 (node)和边 (edge)组成。
在这里插入图片描述
就这么简单!图由节点和边组成。一个节点可能与众多节点直接相连,这些节点被称为邻居。。在前面的欠钱图中,Rama是Alex的邻居。Adit不是Alex的邻居,因为他们不直接相连。但Adit既是Rama的邻居,又是Tom的邻居。图用于模拟不同的东西是如何相连的。下面来看看广度优先搜索。

广度优先搜索
前面介绍了二分查找,广度优先搜索是一种用于图的查找算法,可帮助回答两类问题。
第一类问题:从节点A出发,有前往节点B的路径吗?
第二类问题:从节点A出发,前往节点B的那条路径最短?
在这里插入图片描述
假设你经营着一个芒果农场,需要寻找芒果销售商,以便将芒果卖给他。在Facebook,你与芒果销售商有联系吗?为此,你可在朋友中查找。

在这里插入图片描述
这种查找很简单。首先,创建一个朋友名单。
在这里插入图片描述

然后,依次检查名单中的每个人,看看他是否是芒果销售商。
在这里插入图片描述
假设你没有朋友是芒果销售商,那么你就必须在朋友的朋友中查找。
在这里插入图片描述

检查名单中的每个人时,你都将其朋友加入名单。
在这里插入图片描述
这样一来,你不仅在朋友中查找,还在朋友的朋友中查找。别忘了,你的目标是在你的人际关系网中找到一位芒果销售商,因此,如果Alice不是芒果销售商,就将其朋友也加入到名单中。这意味着你将在她的朋友、朋友的朋友等中查找。使用这种算法将搜遍你的整个人际关系网,直到找到芒果销售商。这就是广度优先搜索算法。

查找最短路径
再说一次,广度优先搜索可回答两类问题。
第一类问题:从节点A出发,有前往节点B的路径吗?(在你的人际关系网中,有芒果销售商吗?)
第二类问题:从节点A出发,前往节点B的哪条路径最短?(哪个芒果销售商与你的关系最近?)
刚才你看到了如何回答第一类问题,下面来尝试回答第二类问题——谁是关系最近的芒果销售商。例如,朋友是一度关系,朋友的朋友是二度关系。
在这里插入图片描述
在你看来,一度关系胜过二度关系,二度关系胜过三度关系,以此类推。因此,你应先在一度关系中搜索,确定其中没有芒果销售商后,才在二度关系中搜索。广度优先搜索就是这样做的!在广度优先搜索的执行过程中,搜索范围从起点开始逐渐向外延伸,即先检查一度关系,再检查二度关系。

你也可以这样看,一度关系在二度关系之前加入查找名单。你按顺序依次检查名单中的每个人,看看他是否是芒果销售商。这将先在一度关系中查找,再在二度关系中查找,因此找到的是关系最近的芒果销售商。广度优先搜索不仅查找从A到B的路径,而且找到的是最短的路径。
在这里插入图片描述
注意,只有按添加顺序查找时,才能实现这样的目的。换句话说,如果Claire先于Anuj加入名单,就需要先检查Claire,再检查Anuj。
如果Claire和Anuj都是芒果销售商,而你先检查Anuj再检查Claire,结果将如何呢?找到的芒果销售商并非是与你关系最近的,因为Anuj是你朋友的朋友,而Claire是你的朋友。因此,你需要按添加顺序进行检查。

有一个可实现这种目的的数据结构,那就是队列 (queue)。

队列
 队列的工作原理与现实生活中的队列完全相同。假设你与朋友一起在公交车站排队,如果你排在他前面,你将先上车。队列的工作原理与此相同。队列类似于栈,你不能随机地访问队列中的元素。队列只支持两种操作:入队 和出队 。
 在这里插入图片描述

如果你将两个元素加入队列,先加入的元素将在后加入的元素之前出队。因此,你可使用队列来表示查找名单!这样,先加入的人将先出队并先被检查。队列是一种先进先出 (First In First Out,FIFO)的数据结构,而栈是一种后进先出 (Last In First Out,LIFO)的数据结构。
在这里插入图片描述
实现算法
先概述一下这种算法的工作原理。
在这里插入图片描述
小结:

  • 广度优先搜索指出是否有从A到B的路径。
  • 如果有,广度优先搜索将找出最短路径。
  • 面临类似于寻找最短路径的问题时,可尝试使用图来建立模型,再使用广度优先搜索来解决问题。
  • 有向图中的边为箭头,箭头的方向指定了关系的方向,例如,rama→adit表示rama欠adit钱。
  • 无向图中的边不带箭头,其中的关系是双向的,例如,ross - rachel表示“ross与rachel约会,而rachel也与ross约会”。
  • 队列是先进先出(FIFO)的。
  • 栈是后进先出(LIFO)的。
  • 你需要按加入顺序检查搜索列表中的人,否则找到的就不是最短路径,因此搜索列表必须是队列。
  • 对于检查过的人,务必不要再去检查,否则可能导致无限循环。

狄克斯特拉算法
继续图的讨论,介绍加权图——提高或降低某些边的权重。介绍狄克斯特拉算法,让你能够找出加权图中前往X的最短路径。介绍图中的环,它导致狄克斯特拉算法不管用。在前一章,你找出了从A点到B点的路径。
在这里插入图片描述
这是最短路径,因为段数最少——只有三段,但不一定是最快路径。如果给这些路段加上时间,你将发现有更快的路径。
在这里插入图片描述
x下面来看看如何对下面的图使用这种算法。
在这里插入图片描述
其中每个数字表示的都是时间,单位分钟。为找出从起点到终点耗时最短的路径,你将使用狄克斯他拉算法。
如果你是用广度优先搜索,将得到下面这条段数最少的路径。
在这里插入图片描述
这条路径耗时7分钟。下面来看看能否找到耗时更短的路径!
狄克斯特拉算法包含4个步骤。
(1) 找出“最便宜”的节点,即可在最短时间内到达的节点。
(2) 更新该节点的邻居的开销,其含义将稍后介绍。
(3) 重复这个过程,直到对图中的每个节点都这样做了。
(4) 计算最终路径。

第一步 :找出最便宜的节点。你站在起点,不知道该前往节点A还是前往节点B。前往这两个节点都要多长时间呢?
在这里插入图片描述
前往节点A需要6分钟,而前往节点B需要2分钟。至于前往其他节点,你还不知道需要多长时间。
在这里插入图片描述
由于你还不知道前往终点需要多长时间,因此你假设为无穷大(这样做的原因你马上就会明白)。节点B是最近的——2分钟就能达到。
第二步 :计算经节点B前往其各个邻居所需的时间。
在这里插入图片描述
你刚找到了一条前往节点A的更短路径!直接前往节点A需要6分钟。
在这里插入图片描述
对于节点B的邻居,如果找到前往它的更短路径,就更新其开销。在这里,你找到了:
前往节点A的更短路径(时间从6分钟缩短到5分钟);
前往终点的更短路径(时间从无穷大缩短到7分钟)。
第三步 :重复!
重复第一步 :找出可在最短时间内前往的节点。你对节点B执行了第二步,除节点B外,可在最短时间内前往的节点是节点A。
在这里插入图片描述

重复第二步 :更新节点A的所有邻居的开销。
在这里插入图片描述
你发现前往终点的时间为6分钟!
你对每个节点都运行了狄克斯特拉算法(无需对终点这样做)。现在,你知道:
前往节点B需要2分钟;
前往节点A需要5分钟;
前往终点需要6分钟。
在这里插入图片描述
术语
介绍其他狄克斯特拉算法使用示例前,先来澄清一些术语。狄克斯特拉算法用于每条边都有关联数字的图,这些数字称为权重 (weight)
在这里插入图片描述

带权重的图称为加权图 (weighted graph),不带权重的图称为非加权图 (unweighted graph)。
在这里插入图片描述
要计算非加权图中的最短路径,可使用广度优先搜索 。
要计算加权图中的最短路径,可使用狄克斯特拉算法 。图还可能有环 ,而环类似右面这样。
在这里插入图片描述
无向图意味着两个节点彼此指向对方,其实就是环!
在这里插入图片描述
在无向图中,每条边都是一个环。狄克斯特拉算法只适用于有向无环图 (directed acyclic graph,DAG)。

术语介绍得差不多了,我们再来看一个例子!这是Rama,想拿一本乐谱换架钢琴。

Alex说:“这是我最喜欢的乐队Destroyer的海报,我愿意拿它换你的乐谱。
如果你再加5美元,还可拿乐谱换我这张稀有的Rick Astley黑胶唱片。”
Amy说:“哇,我听说这张黑胶唱片里有首非常好听的歌曲,我愿意拿我的吉他和架子鼓换这张海报和黑胶唱片。”
Beethoven惊呼:“我一直想要吉他,我愿意拿我的钢琴换Amy的吉他或架子鼓。”
太好了!只要再花一点点钱,Rama就能拿乐谱换架钢琴。现在他需要确定的是,如何花最少的钱实现这个目标。我们来绘制一个图,列出大家的交换意愿。
在这里插入图片描述
这个图中的节点是大家愿意拿出来交换的东西,边的权重是交换时需要额外加多少钱。拿海报换吉他需要额外加30美元,拿黑胶唱片换吉他需要额外加15美元。Rama需要确定采用哪种路径将乐谱换成钢琴时需要支付的额外费用最少。为此,可以使用狄克斯特拉算法!别忘了,狄克斯特拉算法包含四个步骤。在这个示例中,你将完成所有这些步骤,因此你也将计算最终路径。动手之前,你需要做些准备工作:创建一个表格,在其中列出每个节点的开销。这里的开销指的是达到节点需要额外支付多少钱。
在这里插入图片描述
在执行狄克斯特拉算法的过程中,你将不断更新这个表。为计算最终路径,还需在这个表中添加表示父节点 的列。
在这里插入图片描述
这列的作用将稍后介绍。我们开始执行算法吧。
第一步 :
找出最便宜的节点。在这里,换海报最便宜,不需要支付额外的费用。还有更便宜的换海报的途径吗?这一点非常重要,你一定要想一想。Rama能够通过一系列交换得到海报,还能额外得到钱吗?想清楚后接着往下读。答案是不能,因为海报是Rama能够到达的最便宜的节点,没法再便宜了。下面提供了另一种思考角度。假设你要从家里去单位。
在这里插入图片描述
如果你走经过学校的路,到学校需要2分钟。如果你走经过停车场的路,到停车场需要6分钟。如果经停车场前往学校,能不能将时间缩短到少于2分钟呢?不可能,因为只前往停车场就需要6分钟。另一方面,有没有能更快到达停车场的路呢?有。
在这里插入图片描述
这就是狄克斯特拉算法背后的关键理念:
找出图中最便宜的节点,并确保没有到该节点的更便宜的路径 !回到换钢琴的例子。换海报需要支付的额外费用最少。
第二步 :
计算前往该节点的各个邻居的开销。
在这里插入图片描述
现在的表中包含低音吉他和架子鼓的开销。这些开销是用海报交换它们时需要支付的额外费用,因此父节点为海报。这意味着,要到达低音吉他,需要沿从海报出发的边前行,对架子鼓来说亦如此。
在这里插入图片描述
再次执行第一步 :下一个最便宜的节点是黑胶唱片——需要额外支付5美元。
再次执行第二步 :更新黑胶唱片的各个邻居的开销。
在这里插入图片描述
你更新了架子鼓和吉他的开销!这意味着经“黑胶唱片”前往“架子鼓”和“吉他”的开销更低,因此你将这些乐器的父节点改为黑胶唱片。下一个最便宜的是吉他,因此更新其邻居的开销。
在这里插入图片描述
你终于计算出了用吉他换钢琴的开销,于是你将其父节点设置为吉他。最后,对最后一个节点——架子鼓,做同样的处理。
在这里插入图片描述
如果用架子鼓换钢琴,Rama需要额外支付的费用更少。因此,采用最便宜的交换路径时,Rama需要额外支付35美元 。现在来兑现前面的承诺,确定最终的路径。当前,我们知道最短路径的开销为35美元,但如何确定这条路径呢?为此,先找出钢琴 的父节点。
在这里插入图片描述
钢琴的父节点为架子鼓,这意味着Rama需要用架子鼓来换钢琴。因此你就沿着这一边。

我们来看看需要沿哪些边前行。钢琴 的父节点为架子鼓 。
在这里插入图片描述
架子鼓 的父节点为黑胶唱片。
在这里插入图片描述
因此Rama需要用黑胶唱片了换架子鼓。显然,他需要用乐谱来换黑胶唱片。通过沿父节点回溯,便得到了完整的交换路径。
在这里插入图片描述
小结:

  • 广度优先搜索用于在非加权图中查找最短路径。
  • 狄克斯特拉算法用于在加权图中查找最短路径。
  • 仅当权重为正时狄克斯特拉算法才管用。
  • 如果图中包含负权边,请使用贝尔曼-福德算法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值