《算法图解》——第六章 广度有限搜索

       第六章    广度有限搜索

1  图简介

假设你居住在旧金山,要从双子峰前往金门大桥。你想乘公交车前往,并希望换乘最少。可乘坐的公交车如下。

《算法图解》——第六章 广度有限搜索_第1张图片

从双子峰出发,可沿下面的路线三步到达金门大桥。其他的都需要四步。

《算法图解》——第六章 广度有限搜索_第2张图片

这种问题被称为最短路径问题(shortest-path problem),解决最短路径问题的算法被称为广度优先搜索。

如何解决路径问题,需要两个步骤:

①使用图来建立问题模型

②使用广度优先搜索解决问题


 

 

2  图是什么

图模拟一组连接。比如打牌,谁欠谁钱。可以这样表示:

《算法图解》——第六章 广度有限搜索_第3张图片

Alex欠Rama钱,Tom欠Adit钱,等等。图由节点(node,图中圈)和边(edge,图中指向性的线段)组成。一个节点可能与众多节点直接相连,这些节点被称为邻居


 

 

3  广度优先搜索

广度优先搜索是一种用于图的查找算法,可解决两类问题:

①从节点A出发,有前往节点B的路径吗?

②从节点A出发,前往节点B的哪条路径最短?

广度优先搜索的执行过程中,搜索范围从起点开始逐渐向外延伸,即先检查一度关系,再检查二度关系。广度优先搜索不仅查找从A到B的路径,而且找到的是最短的路径。

你需要按添加顺序进行检查。有一个可实现这种目的的数据结构,那就是队列(queue)。


 

 

4  队列

队列类似于栈,你不能随机地访问队列中的元素。队列只支持两种操作:入队和出队。

《算法图解》——第六章 广度有限搜索_第4张图片

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

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

《算法图解》——第六章 广度有限搜索_第5张图片

 

练习

对于下面的每个图,使用广度优先搜索算法来找出答案。

6.1 找出从起点到终点的最短路径的长度。

《算法图解》——第六章 广度有限搜索_第6张图片

 最短路径的长度是2

6.2 找出从cab到bat的最短路径的长度。

《算法图解》——第六章 广度有限搜索_第7张图片

最短路径的长度是2


 

 

5  实现图

图由多个节点组成,每个节点都与相邻节点相连,散列表可以很好的表示这种关系。

散列表让你能够将键映射到值。在这里,你要将节点映射到其所有邻居。如下图:

《算法图解》——第六章 广度有限搜索_第8张图片    《算法图解》——第六章 广度有限搜索_第9张图片

代码表示:

graph = {}
graph["you"] = ["alice", "bob", "claire"]

对于更复杂的呢?

《算法图解》——第六章 广度有限搜索_第10张图片

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"] = []

这被称为有向图(directed graph),其中的关系是单向的。因此,Anuj是Bob的邻居,但Bob不是Anuj的邻居。无向图(undirected graph)没有箭头,直接相连的节点互为邻居。


 

 

6  实现算法(芒果商)

算法的工作原理:

《算法图解》——第六章 广度有限搜索_第11张图片

首先,创建一个队列,可用deque函数创建一个双端队列

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"] = []
search_queue = deque()
search_queue += graph["you"]
def person_is_seller(name):          #判断是否是个芒果商,用名字的m判断,是为了实现代码
    return name[-1] == 'm'
while search_queue:                #列表不为空
    person = search_queue.popleft()     #popleft返回队列左边的第一个
    if person_is_seller(person):       #进行判断,用定义的函数
        print(person + " is a mango seller!")else:
        search_queue += graph[person]    #不是的话继续递归

下面是广度搜索的执行过程:

《算法图解》——第六章 广度有限搜索_第12张图片

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

①找到一位芒果销售商;

②队列变成空的,这意味着你的人际关系网中没有芒果销售商。

这里可能出现无限循环,因为搜索队列将在包含你和包含Peggy之间反复切换。因此,修改代码:

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"] = []
search_queue = deque()
search_queue += graph["you"]
def person_is_seller(name):
    return name[-1] == 'm'
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")

有关运行时间:因为要搜索每一个人,因此每条边都要走过即O(边数),同时,还要对于每一个人要检查,将这个人添加到队列需要时间O(1),因为要对每一个人则是O(人数)。

所以,广度优先搜索的运行时间为O(人数 + 边数),这通常写作O(V + E),其中V为顶点(vertice)数,E为边数。

 

练习:

下面的小图说明了我早晨起床后要做的事情

 《算法图解》——第六章 广度有限搜索_第13张图片

该图指出,我不能没刷牙就吃早餐,因此“吃早餐”依赖于“刷牙”。

另一方面,洗澡不依赖于刷牙,因为我可以先洗澡再刷牙。根据这个图,可创建一个列表,指出我需要按什么顺序完成早晨起床后要做的事情:(1) 起床(2) 洗澡(3) 刷牙(4) 吃早餐

请注意,“洗澡”可随便移动,因此下面的列表也可行:(1) 起床(2) 刷牙(3) 洗澡(4) 吃早餐

6.3 请问下面的三个列表哪些可行、哪些不可行?

《算法图解》——第六章 广度有限搜索_第14张图片

AC不可行,B可行

 

6.4 下面是一个更大的图,请根据它创建一个可行的列表。

《算法图解》——第六章 广度有限搜索_第15张图片

1——起床,2——锻炼,3——洗澡,4——刷牙,5——穿衣服,6——打包午餐,7——吃早餐。

从某种程度上来说,这种列表是有序的。任务A依赖于任务B,在列表中任务A就必须在任务B后面。这被称为拓扑排序,使用它可根据图创建一个有序列表。

 

还有一种结构:,是一种特殊的图,其中没有往后指的边。

6.5 请问下面哪个图也是树?

《算法图解》——第六章 广度有限搜索_第16张图片

AC是数,B不是。树是图的子集,因此树都是图,但图可能是树,也可能不是。


 

 

7  小结

广度优先搜索指出是否有从A到B的路径。如果有,广度优先搜索将找出最短路径。

面临类似于寻找最短路径的问题时,可尝试使用图来建立模型,再使用广度优先搜索来解决问题。

有向图中的边为箭头,箭头的方向指定了关系的方向,例如,rama→adit表示rama欠adit钱。

无向图中的边不带箭头,其中的关系是双向的,例如,ross - rachel表示“ross与rachel约会,而rachel也与ross约会”。

队列是先进先出(FIFO)的。栈是后进先出(LIFO)的。

你需要按加入顺序检查搜索列表中的人,否则找到的就不是最短路径,因此搜索列表必须是队列。

对于检查过的人,务必不要再去检查,否则可能导致无限循环。

 

转载于:https://www.cnblogs.com/NEWzyz/p/8919873.html

你可能感兴趣的:(《算法图解》——第六章 广度有限搜索)