算法图解part6:广度优先搜索

1.图是什么

图由节点node边edge组成。一个节点可能与众多节点直接相连,这些节点被称为邻居。
在这里插入图片描述
拓展:树
树是一种特殊的图,其中没有往后指的边。
单向(根→父亲→儿子)
在这里插入图片描述

2.广度优先搜索、队列

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

  • ①从节点A出发,有前往节点C的路径吗? (寻找朋友中的芒果商)
  • ②从节点A出发,前往节点C的哪条路径最短?(通往金门大桥的最短路径)

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

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

队列:
队列类似于栈,你不能随机地访问队列中的元素。队列只支持两种操作:入队和出队。
队列是一种先进先出(First In First Out,FIFO)的数据结构,而是一种后进先出(Last InFirst Out,LIFO)的数据结构。

队列在python中的简单使用:

from collections import deque
queue = deque(["Eric", "John", "Michael"])
queue.append("Terry")           # Terry 入队
queue.append("Graham")          # Graham 入队
queue.popleft()                 # 队首元素出队
#输出: 'Eric'
queue.popleft()                 # 队首元素出队
#输出: 'John'
print(queue)                           # 队列中剩下的元素
#输出: deque(['Michael', 'Terry', 'Graham'])

运行结果:

deque([‘N’, ‘T’, ‘G’])

3.实现图

举个栗子:
实现下列关系图:
图由多个节点组成,每个节点都与相邻节点相连,散列表可以很好的表示这种关系
散列表让你能够将键(name)映射到值(friends)
在这里插入图片描述
python代码如下:

graph = {}
graph["you"] = ["alice", "bob", "claire"]     #  "you" 为键 , ["alice", "bob", "claire"] 为值
graph["bob"] = ["anuj", "peggy"]
graph["alice"] = ["peggy"]
graph["claire"] = ["thom", "jonny"]
graph["anuj"] = []
graph["peggy"] = []
graph["thom"] = []
graph["jonny"] = []

注:Anuj、Peggy、Thom和Jonny都没有邻居,这是因为虽然有指向他们的箭头,但没有从他们出发指向其他人的箭头。这被称之为有向图(directed graph),其中的关系是单向的。而无向图(undirected graph)没有箭头,直接相连的节点互为邻居,eg:我和BOB互为邻居;我与ANUJ不直接相连不是邻居。

4.实现广度优先搜索算法

概述广度优先搜索算法的工作原理:

在这里插入图片描述
PS:检查一个人之前,要确认之前没检查过他,否则会陷入无限循环。
eg:我不是芒果商,检查我的朋友A,A也不是,将A的朋友——我,加入检查队列,无限循环 “我——A——我”

完整实现代码如下:

from collections import deque

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

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

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")

运行结果:

thom is a mango seller!
True

5.运行时间

在整个人际关系网中搜索芒果商,意味着我将沿着每条边前行(边是一个人到另一个人的箭头或者连接),因此运行时间至少为 O ( 边 数 ) O(边数) O()
此外还有一个队列,包含要检查的人。添加人
入队(压入)
所需时间固定,为 O ( 1 ) O(1) O(1),每个人都这样操作,则时间为 O ( 人 数 ) O(人数) O()
故广度优先搜索的运行时间为 O ( 人 数 + 边 数 ) O(人数+边数) O(+),写作 O ( V + E ) O(V+E) O(V+E) ,其中V为(vertice),E为边数(edge)。

6.总结

  • 广度优先搜索指出是否有A到B的路径
  • 如果有,将找出最短路径
  • 面临类似寻找最短路径的问题时,可尝试使用图来建立模型,再使用广度优先搜索来解决问题
  • 有向图的边为箭头,箭头的方向指定了关系的方向
  • 无向图的边不带箭头,其中的关系是双向的
  • 队列是先进先出的。
  • 栈是后进先出的。
  • 你需要按加入顺序检查搜索列表中的人,否则找到的就不是最短路径,因此搜索列表必须是队列。
  • 对于检查过的人,务必不要再去检查,否则可能导致无限循环。

7.参考资料

《算法图解》第六章

此部分学习算法内容已上传github:https://github.com/ShuaiWang-Code/Algorithm/tree/master/Chapter6

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值