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


           第六章    广度有限搜索

    1  图简介

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

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

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

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

    ①使用图来建立问题模型

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


    2  图是什么

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

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


    3  广度优先搜索

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

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

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

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

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


    4  队列

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

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

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

    练习

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

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

     最短路径的长度是2

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

    最短路径的长度是2


    5  实现图

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

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

        

    代码表示:

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

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


    6  实现算法(芒果商)

    算法的工作原理:

    首先,创建一个队列,可用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]    #不是的话继续递归

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

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

    ①找到一位芒果销售商;

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

    这里可能出现无限循环,因为搜索队列将在包含你和包含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为边数。

    练习:

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

     

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

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

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

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

    AC不可行,B可行

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

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

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

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

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

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


    7  小结

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

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

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

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

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

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

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

  • 相关阅读:
    越长大越孤单
    关于ASP.NET 启动Process的讨论
    利用selenium开发一个功能测试的框架
    开博啦(上班时间)
    利用回发 实现一个简单的AutoComplete功能
    FIFO和双端口RAM
    8位定点数开方程序(贴下来以后研究)
    Eclipse下文件读取的问题:Failed to reading file xxxxx
    Error:NgdBuild:604解决方法(添加NGC文件方法)
    Mandelbrot:美丽的分形
  • 原文地址:https://www.cnblogs.com/NEWzyz/p/8919873.html
Copyright © 2020-2023  润新知