广度优先算法python 广度优先算法流程图_广度优先搜索


广度优先搜索( breadth-first search, BFS)

 学习使用新的数据结构图来建立网络模型。

 学习广度优先搜索,你可对图使用这种算法回答诸如“到X的最短路径是什么”等问题。

 学习有向图和无向图。

 学习拓扑排序,这种排序算法指出了节点之间的依赖关系。

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

图由节点( node) 和边( edge) 组成。


广度优先算法python 广度优先算法流程图_搜索_02


一个节点可能与众多节点直接相连,这些节点被称为邻居。


广度优先搜索

广度优先搜索是一种用于图的查找算法,可以回答两种问题:

 第一类问题:从节点A出发,有前往节点B的路径吗?

 第二类问题:从节点A出发,前往节点B的哪条路径最短?

例如,朋友是一度关系,朋友的朋友是二度关系。


广度优先算法python 广度优先算法流程图_广度优先算法_03


广度优先算法python 广度优先算法流程图_广度优先算法python_04


在你看来,一度关系胜过二度关系,二度关系胜过三度关系,以此类推。因此,你应先在一度关系中搜索,确定其中没有芒果销售商后,才在二度关系中搜索。广度优先搜索就是这样做的。

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


队列Queue

只有按添加顺序查找时,才能实现这样的目的。有一个可实现这种目的的数据结构,那就是队列( queue) 。

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


广度优先算法python 广度优先算法流程图_广度优先算法python_05


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

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


广度优先算法python 广度优先算法流程图_最短路径_06



实现图

每个节点都与邻近节点相连,如果表示类似于“你→Bob”这样的关系呢?使用散列表(字典)。


广度优先算法python 广度优先算法流程图_广度优先算法python_07


广度优先算法python 广度优先算法流程图_广度优先算法_08


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


因此graph["you"]是一个数组,其中包含了“你”的所有邻居。


广度优先算法python 广度优先算法流程图_广度优先搜索_09


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


请注意:

  1. 可以认为是有向图( directed graph),所以这里记录的是有方向的映射关系bob->peggy有映射,但是peggy到bob无映射。

2. 散列表是无序的,因此添加键—值对的顺序无关紧要。


广度优先算法python 广度优先算法流程图_广度优先搜索_10


有向图( directed graph)其中的关系是单向的。无向图( undirected graph) 没有箭头,直接相连的节点互为邻居。


实现算法

算法思想如图所示:


广度优先算法python 广度优先算法流程图_搜索_11


from collections import deque
#deque(double-ended queue,双端队列)是一种具有队列和栈的性质的数据结构。
#双端队列中的元素可以从两端弹出,相比list增加[]运算符重载。

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

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 #如果执行到了这里,就说明已经执行完了while循环
             #但是依旧没有return True,说明队列中没人是芒果销售商


def person_is_seller(name):
    return name[-1] == 'm' #这个函数检查人的姓名是否以m结尾:如果是,他就是芒果销售商。


广度优先搜索的执行过程图示:


广度优先算法python 广度优先算法流程图_搜索_12


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

 找到一位芒果销售商;

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

同时需要注意的是,在进行广度优先搜索的时候,可能会遇见之前已经访问过的人(结点),为了避免重复访问,当检查完一个人后,应将其标记为已检查,且不再检查他。

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


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()函数
search("you")



运行时间分析

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

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


小结

 广度优先搜索指出是否有从A到B的路径。

 如果有,广度优先搜索将找出最短路径。

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

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

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

 队列是先进先出( FIFO)的。

 栈是后进先出( LIFO)的。

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

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