一、对BFS的初步理解(目的为求最短距离)
BFS 广度优先算法,通过每一个节点的扩散进行搜索
BFS(graph,s)可以在graph中从s开始执行BFS广搜。
BFS不会重复遍历一个曾经访问过的节点(因为各自距离均为1个边,曾经遍历过说明已经有一个点到现在这个点的距离为1个边,若目的是求最小路径,那么两种解实际上相同)
BFS需要通过一个数组queue存储等待遍历的值,从后往前存储,从前往后取值(也就是保证优先性,即先取到的点优先遍历)所以实际上是有序的,也正是因为有序性,BFS可以用作最短距离的求取。
(图片取自灯神)
历程:例如从a点进行BFS,先将A放入queue(queue=['A']),首先遍历A(弹出,此时queue = []),看A点连接的点:B,C,判断B,C均未被访问,所以将B,C从后放入数组(['B','C']),并将A,B,C标记为已访问。其次遍历B(弹出,此时queue = ['C']),看B点连接的点:A,C,D 由于A,C点被访问,D未被访问,只将D从后往前放入queue中(queue = ['C','D'])并将D标记为已访问。其次遍历C(弹出 queue = ['D']),发现C连接的点:A,B,D,E 由于A,B,D被访问,只插入E(queue = ['D','E'])并将E标记为已访问然后遍历D(弹出),BCE已访问所以只有F,(queue = ['E','F'])后续遍历弹出E,F无效遍历[queue=[]]queue为空自动停止循环。
def BFS(graph,s):
queue = []
queue.append(s)
seen = []
seen.append(s)
while queue:
vertex = queue.pop(0)
for i in graph[vertex]:
if i not in seen:
queue.append(i)
seen.append(i)
print(vertex)
graph={
"A":["B","C"],
"B":["A","C","D"],
"C":["A","B","D","E"],
"D":["B","C","E","F"],
"E":["C","D"],
"F":["D"]
}
若增加一个字典parent = {s:None}(设定起点的前一个点为none)(s为BFS输入的元素)
在每次遍历弹出元素a并引出节点使parent[节点] = a 字典则会输出一个记录每个点的上一个节点的字典 例如{'A': None, 'B': 'A', 'C': 'A', 'D': 'B', 'E': 'C', 'F': 'D'}是以A为源点遍历的
只要记录了每个节点的上一个节点就相当于记录了距离节点为1的一个点,若反向输出,即例如先输出E的上一个节点C,再输出C的上一个节点A,直到A的上一个节点为None时停止输出,就会得到一条最近的路径(不会输出全部,但是可以保证输出一定为最优解)
def BFS(graph,s):
queue = []
queue.append(s)
seen = []
seen.append(s)
parent={s:None}
while queue:
vertex = queue.pop(0)
for i in graph[vertex]:
if i not in seen:
queue.append(i)
seen.append(i)
parent[i]=vertex
print(vertex)
return parent
graph={
"A":["B","C"],
"B":["A","C","D"],
"C":["A","B","D","E"],
"D":["B","C","E","F"],
"E":["C","D"],
"F":["D"]
}
def output(v):
while v!= None:
print(v)
v=parent[v]
二、BFS向dijkstra的延申
BFS输出最短路径只需要在连在同一个源点的节点里随意找一个人为规定的节点作为第一遍历点即可,因为他们的地位是相同的,到源点的距离均为1(例如遍历A获得B,C后先遍历B还是C都能得到最短路径)。seen中每次遍历都标记为访问也是因为地位相同,同一层的节点到下一层的距离相同,遍历所有情况只会浪费资源,而不会出现更好的路径。
而对于有权值的图,即路之间有距离,就需要优先遍历距离小的点
总结延申了两点:
①从距离较近的点进行遍历
因为遍历后源节点会被标为已访问,即已经得到到此点最近的距离,所以我们要在保证得到此距离时进行遍历,且遍历时存储的是此节点最近的距离(点,最短距离)
遍历一个点时会得到经过这个点到其他点的距离并写入queue进行排序。也就是说优先遍历距离更小的节点有可能会有到queue后续点的更短的路径,只需要这个节点到目标节点的距离小于目标节点在queue中记录的最小距离减这个节点到起始节点(‘A’)距离。这是有可能发生的,所以优先遍历。即使并未得到最小距离,也考虑过这种情况了。而本身到起始节点就大的值若比后续遍历的点距离大,一定不会产生到后续节点的更小的值,因为他们已经有一个比遍历点到起始距离更小的距离值,不会有经过遍历点的更小的路径,只会维持起初的路径。而当一个节点成为queue中最小距离点时,进行遍历,就可以保证此时这个点已经有了最小的距离。
②seen中保留遍历过的点
遍历时是queue中的最小距离点,此时不会产生到此点的更小路径,遍历时加入seen防止再次遍历
代码实现思路:
在BFS上对于以上两点进行延申,主要为:
①新增一个数组记录所有点到源点的最近距离(dp思想,记录最优解)
②import heapq (heap.heappush(pqueue,s)将s导入pqueue(heap.heappop(pqueue))将pqueue中代最小值的点pop出)注意:pqueue中元素的形式为(a,b)a为数值,数值越小越早pop出,b为实际值
利用heapq实现有序弹出,从距离较近的点开始遍历
大体思路自然语言概述(以A举例):
创造pqueue,seen,distance,parent将(0,A)放入到pqueue中,将'A':None放入parent中代表A之前没有数据,将distance中A点标记为0,其余点标记为正无穷(9999999)。
若pqueue有值,从pqueue中弹出最小值进行遍历,先遍历A,若A没有出现在seen中,继续,将A放入seen中,记录A到源点的距离(0),依次判断与A相连的节点i,若i没有出现在seen中,继续,记录该点i经过A到源点的距离disi(A到源点的距离+A到该点的距离),把(disi,i)从后往前放入pqueue中(heappush)。如果disi比distance中记录i距离源点最小值还小,则将disi记录为最小值,并且将parent中记录最优路径中i之前最优点的值改为A。
代码实现:
import heapq
def p(graph,s):#将起点标记为0,其他点到起点的距离标记为正无穷
distance = {}
for i in graph.keys():
distance[i] = 9999999
distance[s] = 0
return distance
def diskstra(graph,s):
pqueue = []
heapq.heappush(pqueue,(0,s))
seen = []
parent = {s:None}
distance = p(graph,s)#需要存储到所有点的最近距离的值
while pqueue:#只要pqueue中有值就会循环
pair = heapq.heappop(pqueue)#取出一个元组
dis = pair[0]
vertex = pair[1]
if vertex in seen:#根据灯神的代码改良,如果曾经遍历过这个节点,就直接开始下一次循环
continue
seen.append(vertex)
for i in graph[vertex].keys():
if i not in seen:
disi = graph[vertex][i] + dis
heapq.heappush(pqueue,(disi,i))
if disi < distance[i]:
parent[i]=vertex
distance[i] = disi
return [parent,distance,seen]
graph={
"A":{"B":5,"C":1},
"B":{"A":5,"C":2,"D":1},
"C":{"A":1,"B":2,"D":4,"E":8},
"D":{"B":1,"C":4,"E":3,"F":6},
"E":{"C":8,"D":3},
"F":{"D":6}
}
def output(graph,s,v):
while v!= None:
print(v)
v=diskstra(graph,s)[0][v]