算法图解-广度优先搜索

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

算法图解-广度优先搜索_第1张图片
image.png

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

算法图解-广度优先搜索_第2张图片
1-1

为找出换乘最少的乘车路线,你将使用什么样的算法?
算法图解-广度优先搜索_第3张图片
1-2

还有其他前往金门大桥的路线,但它们更远(需要四步)。这个算法发现,前往金门大桥的最短路径需要三步。这种问题被称为最短路径问题(shorterst-path problem)。你经常要找出最短路径,这可能是前往朋友家的最短路径,也可能是国际象棋中把对方将死的最少步数。 解决最短路径问题的算法被称为广度优先搜索。
要确定如何从双子峰前往金门大桥,需要两个步骤。
(1) 使用图来建立问题模型。
(2) 使用广度优先搜索解决问题。

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

  • 第一类问题:从节点A出发,有前往节点B的路径吗?
  • 第二类问题:从节点A出发,前往节点B的哪条路径最短?
    注意: 广度优先搜索需要按添加顺序进行检查。有一个可实现这种目的的数据结构,那就是队列(queue)。

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

算法图解-广度优先搜索_第4张图片
3-1

如果你将两个元素加入队列,先加入的元素将在后加入的元素之前出队。因此,你可使用队列来表示查找名单!这样,先加入的人将先出队并先被检查。
队列是一种先进先出(First In First Out,FIFO)的数据结构,而栈是一种后进先出(Last InFirst Out,LIFO)的数据结构。
算法图解-广度优先搜索_第5张图片
3-2

4. 算法实现
先概述一下这种算法的工作原理。

算法图解-广度优先搜索_第6张图片
4-1

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

    算法图解-广度优先搜索_第7张图片
    4-2

    完整代码如下
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"] = []

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

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

while search_queue: # 只要队列不为空
    person = search_queue.popleft() # 就取出其中的第一个人
    if person_is_seller(person): # 检查这个人是否是芒果商
        print (person + " is a mango seller!") # 是芒果商

    else:
        search_queue += graph[person] # 不是芒果商,将这个人的朋友都加入搜索队列

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

  • 找到一位芒果销售商;
  • 队列变成空的,这意味着你的人际关系网中没有芒果销售商。
    Peggy既是Alice的朋友又是Bob的朋友,因此她将被加入队列两次:一次是在添加Alice的朋友时,另一次是在添加Bob的朋友时。因此,搜索队列将包含两个Peggy。
    算法图解-广度优先搜索_第8张图片
    4-3

    但你只需检查Peggy一次,看她是不是芒果销售商。如果你检查两次,就做了无用功。因此,检查完一个人后,应将其标记为已检查,且不再检查他。如果不这样做,就可能会导致无限循环。
    完善后的算法
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"] = []

from collections import deque
def person_is_seller(name): 
    '这个函数检查人的姓名是否以m结尾:如果是,他就是芒果销售商'
    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!")
            else:
                search_queue += graph[person]
                searched.append(person) # 将这个人标记为检查过

search("you")

**5. 运行时间**
如果你在你的整个人际关系网中搜索芒果销售商,就意味着你将沿每条边前行(记住,边是从一个人到另一个人的箭头或连接),因此运行时间至少为O(边数)。
你还使用了一个队列,其中包含要检查的每个人。将一个人添加到队列需要的时间是固定的,即为O(1),因此对每个人都这样做需要的总时间为O(人数)。所以,广度优先搜索的运行时间为O(人数 + 边数),这通常写作O(V + E),其中V为顶点(vertice)数,E为边数。

你可能感兴趣的:(算法图解-广度优先搜索)