02 《算法图解——像小说一样有趣的算法书》2


6 广度优先搜索

广度优先搜索(breadth-first search,BFS),广度优先搜索能够找出两样东西之间的最短距离,例如:
① 编写国际跳棋AI,计算最少走多少步可获胜;
② 编写拼写检查器,计算最少编辑多少个地方就可将错拼的单词改写成正确的单词,如将READED改为READER需要一个编辑的地方;
③根据你的人际关系网络找到关系最近的医生。
这种问题被称为最短路径问题(shortest-path problem),解决这种问题的算法被称为广度优先搜索

解决该问题需要两个步骤
① 使用图来建立问题模型。
② 使用广度优先搜索解决问题。

广度优先搜索可以回答两个问题
① 第一类问题:从节点A出发,有前往节点B的路径吗?
(在你的人际关系中,有芒果销售商吗?)
② 第二类问题:从节点A出发,前往节点B的哪条路径最短?
(哪个芒果销售商与你的关系最近?)
一度关系胜过二度关系,二度关系胜过三度关系,以此类推。你应先在一度关系中搜索,确定其中没有芒果销售商后,才在二度关系中搜索。


队列 Queue
如果你将几个元素加入队列,先加入的元素将在后加入的元素之前出队,队列只支持两种操作:出队和入队。
队列是一种先进先出(First In First Out,FIFO)的数据结构,而栈是一种后进先出(Last In First Out,LIFO)的数据结构。

示例

在你的朋友圈中,找到芒果销售商。
寻找芒果销售商

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"]				//<---------将你的邻居添加到这个队列中
while search_queue:						//<---------只要队列不为空
	person=search_queue.popleft()		//<---------取出一个人
	if person_is_seller(person):		//<---------检查这个人是否是芒果销售商
		print(person+" is a mango seller ! ")	//<-是芒果销售商
		return True
	else:
		search_queue.append(graph[person])	//<-----不是芒果销售商。将这个人的朋
		//search_queue+=graph[person]								友都加入队列 
return False							//<-----如果达到了这里,就说明队列中没人是芒果销售商

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

  • 找到一位芒果销售商;
  • 队列变成空的,这意味着你的人际关系网中没有芒果销售商。

Peggy即使Alice的朋友又是Bob的朋友,因此她将被加入队列两次:一次是在添加Alice的朋友时,另一次是在添加Bob朋友时。因此,搜索队列将包含两个Peggy。你只需检查Peggy一次,看她是不是芒果销售商。如果检查两次,就做了无用功。因为检查完一个人后,应将其标记为已检查,且不再检查他。(检查一个人之前,要确认之前没检查过他,这很重要。为此,你可使用一个列表来记录检查过的人。)

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

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_a_seller(person):
				print(person + " is a seller ! ")
			else:
				search_queue.append(graph[person])	//<---search_queue+=graph[person]
				searched.append(person)
	return False

search("you")

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


7 狄克斯特拉算法

广度优先算法(breadth-first search,BFS):找出段数最少的路径。
在广度优先算法(breadth-first search,BFS)

狄克斯特拉算法(Dijkstra’s algorithm):找出最快的路径。
狄克斯特拉算法(Dijkstra's algorithm)
使用广度优先搜索来查找两点之间的最短路径,那时“最短路径”的意思是段数最少。在狄克斯特拉算法中,你给每段都分配了一个数字或权重,因此狄克斯特拉算法找出的是总权重最小的路径。

狄克斯特拉算法包含的4个步骤:
① 找出最便宜的节点,即可在最短时间内前往的节点。
② 对于该节点的邻居,检查他们是否有前往它们的更短路径,如果有,就更新其开销。
③ 重复这个过程,直到对图中的每个节点都这样做了。
④ 计算最终路径。

狄克斯特拉算法用于每条边都有关联数字的图,这些数字成为权重(weight)。带权重的图成为加权图(weighted graph),不带权重的图称为非加权图(unweighted graph)。
权重:
权重
加权图:                                                                       非加权图:
在这里插入图片描述
要计算非加权图中的最短路径,可使用广度优先搜索。要计算加权图中的最短路径,可使用狄克斯特拉算法。图还可能有环,狄克斯特拉算法只适用于有向无环图(directed acyclicgraph,DAG)。

狄克斯特拉算法关键理念:找出图中最便宜的节点,并确保没有到该节点的更便宜的路径!
最短路径不一定指的是物理距离,也可能是让某种度量指标最小。

负权边:权重出现负数的情况。如果出现负权边,就不能使用狄克斯特拉算法。
因为狄克斯特拉算法有这样的假设:对于处理过的节点,没有前往该节点的更短路径。这种假设仅在没有负权边时成立。因此,不能将狄克斯特拉算法用于包含负权边的图。在包含负权边的图中,要找出最短路径,可使用另一种算法——贝尔曼-福德算法(Bellman-Ford algorithm)(本书不介绍)。



如何用代码实现狄克斯特拉算法:
示例:在这里插入图片描述
要编写解决这个问题,需要三个列表。在这里插入图片描述
随着算法的进行,你将不断更新散列表costs和parents。

//===========================  表示整个图的散列表graph={}  ============================//

graph={}
graph["start"]={}				//<----------"start"为起始节点(起点)
graph["start"]["a"]=6				//<----------"start""a"的权重为6
graph["start"]["b"]=2				//<----------"start""b"的权重为2

//------------------------------------------------------------------------------------//

>>>print(graph)
>{'start': {'a': 6, 'b': 2}}
>>>print(graph["start"])
>{'a': 6, 'b': 2}
>>>print(graph["start"].keys())
>dict_keys(['a', 'b'])
>>>print(graph["start"]["a"])
>6
>>>print(graph["start"]["b"])
>2

//------------------------------------------------------------------------------------//

graph["a"]={}					//<----------"a"为中间节点
graph["a"]["fin"]=1					//<----------"a""fin"的权重为1

graph["b"]={}					//<----------"b"为中间节点
graph["b"]["a"]=3					//<----------"b""a"的权重为3
graph["b"]["fin"]=5					//<----------"b""fin"的权重为5

graph["fin"]={}					//<----------"fin"为终止节点(终点)

//------------------------------------------------------------------------------------//

>>>print(graph)
>{'start': {'a': 6, 'b': 2}, 'a': {'fin': 1}, 'b': {'a': 3, 'fin': 5}, 'fin': {}}

//==============================  创建开销列表costs={}  ===============================//

infinity=float("inf")
costs={}
costs["a"]=6
costs["b"]=2
costs["fin"]=infinity

>>>print(costs)
>{'a': 6, 'b': 2, 'fin': inf}

//================================  储存父节点的散列表  ===============================//

parents={}
parents["a"]="start"
parents["b"]="start"
parents["fin"]=None

//========  你需要一个数组,用于记录处理过的节点,因为对同一个节点,你不可处理多次。 =======//

processed=[]

//=======================================  算法  =====================================//

node=find_lowest_cost_node(costs)			//<----在未处理的节点中找出开销最小的节点
while node is not None: 					//<----while循环在所有节点被处理后结束
	cost=costs[node]
	neighbors=graph[node]
	for n in neighbors.keys():
		new_cost=cost+neighbors[n]
		if costs[n]>new_cost:
			costs[n]=new_cost
			parents[n]=node
	processed.append(node)
	node=find_lowest_cost_node(costs)

//=============================  find_lowest_cost_node()  ============================//

def find_lowest_cost_node(costs):
	lowest_cost=float("inf")
	lowest_cost_node=None
	for node in costs:
		cost=costs[node]
		if cost<lowest_cost and node not in processed:
			lowest_cost=cost
			lowest_cost_node=node
	return lowest_cost_node

//------------------------------------------------------------------------------------//

注意
Python中表示正负无穷用下面代码:
float("inf")float("-inf")

小结
(1)广度优先搜索用于非加权图中查找最短路径。
(2)狄克斯特拉算法用于在加权图中查找最短路径。
(3)仅当权重为正时,狄克斯特拉算法才管用。
(4)如果图中包含负权边,请用贝尔曼-福德算法。


《算法图解——像小说一样有趣的算法书》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值