编程语言
首页 > 编程语言> > 【算法】广度优先搜索——最短路径问题

【算法】广度优先搜索——最短路径问题

作者:互联网

目录

1. 广度优先搜索

1.1 查找最短路径

1.2 队列

2. 算法实现

3. 运行时间


1. 广度优先搜索

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

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

当要计算地点A到地点B的最短路径时,你可以使用广度优先搜索。这个问题属于第二类问题:哪条路径最短?下面来详细研究这个算法,你将使用它来回答第一类问题:有路径吗?

假设你经营着一个芒果农场,需要寻找芒果经销商,以便将芒果卖给他。然而,你并不认识芒果经销商。为此,你可以在朋友中查找。

这种查找很简单。首先,创建一个朋友清单。

然后,一次检查名单中的每个人,看看他是否是芒果经销商。

假设你没有朋友是芒果经销商,那么你就必须在朋友的朋友中查找。

检查名单中的每个人时,你都将其朋友加入名单。

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

1.1 查找最短路径

再次重复,广度优先搜索可回答两类问题:

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

刚才你看到了如何回答第一类,下面来尝试回答第二类问题——谁是关系最近的芒果经销商。例如,朋友是一度关系,朋友的朋友是二度关系。

在你看来,一度关系胜过二度关系,二度关系胜过三度关系,以此类推。因此,你应先在一度搜索中搜索,确定没有芒果经销商后,才在二度关系中搜索。广度优先搜索就是这样做的!在广度优先搜索的执行过程中,搜索范围从起点开始逐渐向外延伸,即先检查一度关系,再检查二度关系。顺便问一句:将先检查Claire还是Anuj呢? Claire是一度关系,而Anuj是二度关系,因此将先检查Claire,后检查Anuj。

你也可以这样看,一度关系在二度关系之前加入查找名单。

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

注意,只有按添加顺序查找时,才能实现这样的目的。换句话说,如果Claire先于Anuj加入名单就需要先检查Claire,再检查Anuj。如果Claire和Anuj都是芒果经销商,而你先检查Anuj再检查Claire,结果将会如何呢?中安到的芒果经销商并非是与你关系最近的,因为Anuj是你朋友的朋友,而Claire是你的朋友。因此,你需要按添加顺序进行检查。有一个可实现这种目的的数据结构,那就是队列(queue)

1.2 队列

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

如果你将两个元素加入队列,先加入的元素将在后加入的元素之前出队。因此,你可使用队列来表示查找名单!这样,先加入的人将先出队并先被检查。

队列是一种先进先出(First In First Out, FIFO)的数据结构,而栈是一种后进先出(Last In First Out,LIFO)的数据结构。

知道队列的工作原理之后,即可实现广度优先搜索。

2. 算法实现

先概述一下这种算法:

首先,创建一个队列。在Python中,可使用函数deque来创建一个双端队列。

from collection import deque
search_queue = deque()    #创建一个队列
search_queue += graph["you"]    #将你的邻居都加入到这个搜索队列中

别忘了,graph["you"]是一个数组,其中包含你的所有邻居,如["alice", "bob", "claire"]。这些邻居都将加入到搜索队列中。

下面来看看其他的代码。

while search_queue:    #只要队列不为空
    person = search_queue.popleft()    #取出其中的第一个人
    if person_is_seller(person):    #检查这个人是否是芒果经销商
        print person + " is a mango seller!"    #是芒果经销商
        return True
    else:
        search_queue += graph[person]    #不是芒果经销商,将这个人的朋友都加入搜索队列
return False    #如果达到了这里,就说明队列中没人是芒果经销商

最后,你还需要编写函数person_is_seller,判断一个人是否是芒果经销商。如下所示:

def person_is_seller(name):
    return name[-1] == 'm'

这个函数仅通过检查人的姓名是否是以m结尾:如果是,他就是芒果销售商。这种判断方法有点搞笑,但就这个示例而言是可行的。下面来看看广度优先搜索的执行过程。

这个算法将不断执行,直到满足以下条件之一:

  1. 找到一位芒果供应商;
  2. 队伍变成空的,这意味着你的人际关系网中没有芒果经销商。

Peggy既是Alice的朋友又是Bob的朋友,因此她将被加入队列两次:一次是在添加Alice的朋友时,另一次是在添加Bob的朋友时。因此,搜索队列将包含两个Peggy。

但你只需要检查Peggy一次,看她是不是芒果经销商。如果你检查两次,就做了无用功。因此,检查完一个人后,应将其标记为已检查,且不再检查他。

如果不这样做,就可能会导致无限循环,假设你的人际关系网类似于下面这样。

一开始,搜索队列包含你的所有邻居。

现在你检查Peggy。他不是芒果经销商,因此你将其所有邻居都加入搜索队列。

接下来,你检查自己。你不是芒果经销商,因此你将你所有的邻居都加入到搜索队列。

以此类推,将形成无限循环,因此搜索队列将在包含你和包含Peggy之间反复切换。

检查一个人之前,要确认之前没检查过他,这很重要。为此,你可使用一个列表来记录检查过的人。

考虑到这一点后,广度优先搜索的最终代码如下:

def search(name):
    search_queue = deque()
    search_queue += graph[name]
    searched = []      #这个数组用于记录检查过的人
    while search_queue:
        person = search_queue.popleft()
        if not person in searched:    #仅当这个人没检查过时才检查
            if person_is_seller(person):
                print person + " is a mango seller!"
                return True
        else:
            search_queue += graph[person]
            searched.append(person)    #将这个人标记为检查过
    return False


search("you")

3. 运行时间

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

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

 

 

标签:经销商,检查,队列,最短,芒果,算法,person,搜索,广度
来源: https://blog.csdn.net/weixin_46318945/article/details/110001201