今天是图论的学习,就从最短路算法开始叭
Dijkstra算法是典型的单源最短路算法
,即求图中一个点到其他所有点的最短路径的算法,时间复杂度 O ( n 2 ) O(n^2) O(n2)
Dijkstra算法算是贪心思想实现的,图不能有负权边,其核心要点为:
每次从 「未求出最短路径的点」
中 取出 「离起点距离最短」
路径的点,并以这个点为当前节点 刷新「未求出最短路径的点」
的距离
以下图为例:
Dijkstra 算法将会寻找出图中 节点0
到所有其他节点
的最短路径。
起点到每个点的距离:
节点 | 当前节点 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|---|
step1 | − - − | 0 0 0 | ∞ \infty ∞ | ∞ \infty ∞ | ∞ \infty ∞ | ∞ \infty ∞ | ∞ \infty ∞ | ∞ \infty ∞ |
step2 | 0 0 0 | 0 0 0 | 2 2 2 | 6 6 6 | ∞ \infty ∞ | ∞ \infty ∞ | ∞ \infty ∞ | ∞ \infty ∞ |
step3 | 1 1 1 | 0 0 0 | 2 2 2 | 6 6 6 | 7 7 7 | ∞ \infty ∞ | ∞ \infty ∞ | ∞ \infty ∞ |
step4 | 2 2 2 | 0 0 0 | 2 2 2 | 6 6 6 | 7 7 7 | ∞ \infty ∞ | ∞ \infty ∞ | ∞ \infty ∞ |
step5 | 3 3 3 | 0 0 0 | 2 2 2 | 6 6 6 | 7 7 7 | 17 17 17 | 22 22 22 | ∞ \infty ∞ |
step6 | 4 4 4 | 0 0 0 | 2 2 2 | 6 6 6 | 7 7 7 | 17 17 17 | 20 20 20 | 27 27 27 |
step7 | 5 5 5 | 0 0 0 | 2 2 2 | 6 6 6 | 7 7 7 | 17 17 17 | 20 20 20 | 26 26 26 |
step8 | 6 6 6 | 0 0 0 | 2 2 2 | 6 6 6 | 7 7 7 | 17 17 17 | 20 20 20 | 26 26 26 |
代码
def dijkstra(s):
# 标记数组:used[v]值为False说明改顶点还没有访问过,在S中,否则在U中!
used = [False for _ in V]
# 距离数组:distance[i]表示从源点s到i的最短距离,先把所有节点的距离初始化为无穷
distance = [float('inf') for _ in V]
distance[s] = 0
while True:
# v在这里相当于是一个哨兵,对包含起点s做统一处理!
v = -1
# 从未使用过的顶点中选择一个距离最小的顶点
for u in V:
if not used[u] and (v == -1 or distance[u] < distance[v]):
v = u
if v == -1:
# 说明所有顶点都维护到S中了!
break
# 将选定的顶点加入到S中, 同时进行距离更新
used[v] = True
# 更新U中各个顶点到起点s的距离。
for u in V:
distance[u] = min(distance[u], distance[v] + cost[v][u])
Bellman-Ford是单源最短路径问题
的一种算法。它的原理是对图进行 n − 1 n-1 n−1 次松弛操作,得到所有可能的最短路径。
其优于Dijkstra算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高。
实现步骤:
代码:
def bellman_ford(graph, source):
dist = {}
p = {}
max = 10000
for v in graph:
dist[v] = max #赋值为负无穷完成初始化
p[v] = None
dist[source] = 0
for i in range(len( graph ) - 1):
for u in graph:
for v in graph[u]:
if dist[v] > graph[u][v] + dist[u]:
dist[v] = graph[u][v] + dist[u]
p[v] = u #完成松弛操作,p为前驱节点
for u in graph:
for v in graph[u]:
if dist[v] > dist[u] + graph[u][v]:
return None, None #判断是否存在环路
return dist, p
int dist[N],backup[N];//dist距离,backup用来存上一次的结果。
struct edge//用来存边
{
int a;
int b;
int w;
}Edge[M];
int Bellman_Ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;//初始化
for(int i = 0 ; i < k ; i++)//遍历k次
{
memcpy(backup,dist,sizeof dist);//存上一次答案。
for(int j = 0 ; j < m ; j++)
{
int a = Edge[j].a, b = Edge[j].b, w = Edge[j].w;
dist[b] = min(dist[b],backup[a] + w);
}//遍历所有边
}
if(dist[n] > 0x3f3f3f3f/2) return -1;
/*这里不像Dijkstra写等于正无穷是因为可能有负权边甚至是负环的存在,
使得“正无穷”在迭代过程中受到一点影响。*/
return dist[n];
}
Floyd 算法是典型的多源最短路算法
, 求 所有点到所有点
的最短路径的算法
核心要点:
要点:以每个点为 「中转站」 , 刷新所有 「入度」 和 「出度」 的距离。
遍历每一个顶点 --> 遍历点的每一个入度 --> 遍历每一个点的出度,以这个点为「中转站」距离更短就刷新距离
(比如 B 点为中转站 AB + BD < AD 就刷新 A 到 D 的距离)
代码
for k in range(n):
for i in range(n):
for j in range(n):
graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j])
A*算法是一种启发式搜索算法,利用合适的启发函数,全面评估各扩展搜索节点的代价值,通过比较扩展节点代价值的大小,选择价值最小的点加以扩展,直到找到目标节点为止。
和 Dijkstra 算法相比,A* 采用启发式的搜索策略,能够更快地搜索出最短路径
核心的模型评估函数是:
f ( n ) = g ( n ) + h ( n ) f(n)=g(n)+h(n) f(n)=g(n)+h(n)
算法流程:
openList 表
: 记录准备处理的点,根据f(n)升序排序closeList 表
: 记录最短路径上的所有点的信息估计距离的对比
代码
def planning(self, sx, sy, gx, gy):
# 设置起点,先获得起点所在的格子,然后起点的花费初始化为0,父节点初始化为-1
start_node = self.Node(self.get_xy(sx, self.min_x), self.get_xy(sy, self.min_y), 0.0, -1)
# 设置终点,先获得终点所在的格子,然后起点的花费初始化为0,父节点初始化为-1
goal_node = self.Node(self.get_xy(gx,self.min_x), self.get_xy(gy, self.min_y), 0.0, -1)
start_node.distance = self.get_distance(goal_node, start_node) # 初始化起点的估计距离
q = PriorityQueue() # 定义一个优先队列
q.put(start_node) # 起点入队
closed_set = {}
dis = {} # 记录起点到每个点的距离
n_id = self.get_node(start_node)
dis[n_id] = 0
while not q.empty():
current = q.get() # 获取队头元素加出队
id = self.get_node(current)
if id in closed_set: continue
closed_set[id] = current # 把这个点加入close_set中
# 如果到达了终点
if current.x == goal_node.x and current.y == goal_node.y:
goal_node.parent_index = current.parent_index
goal_node.cost = current.cost
break
for i,_ in enumerate(self.motion):
node = self.Node(current.x + self.motion[i][0], current.y + self.motion[i][1], current.cost + self.motion[i][2], id)
now_id = self.get_node(node)
# 如果close_set中已经有它了, 不会重复添加和更新
if now_id in closed_set: continue
# 如果不满足合法条件
if not self.verify_node(node): continue
node.distance = node.cost + self.get_distance(node,goal_node)
if(now_id not in dis):
dis[now_id] = node.cost
q.put(node)
elif(node.cost < dis[now_id]):
dis[now_id] = node.cost
q.put(node)
# 获得路径
rx, ry = self.get_path(goal_node, closed_set)
return rx, ry
有向图最短路
% 图的邻接矩阵
E = [1,2,6;1,3,3;1,4,1;2,5,1;3,2,2;3,4,2;4,6,10;5,4,6;
5,6,4;5,7,3;5,8,6;6,5,10;6,7,2;7,8,4;9,5,2;9,8,3];
% 边的一端,边的另一端,边的权值
G = digraph(E(:,1),E(:,2),E(:,3));
% 找出最短路和花费的费用
[path,d] = shortestpath(G,1,8,'method','positive');
% 画出图
p = plot(G,'EdgeLabel',G.Edges.Weight,'Layout','circle');
% 高亮出最短路径
highlight(p,path,'EdgeColor','red','LineWidth',1.5);
a = zeros(11);
a(1,2)=2;a(1,3)=8;a(1,4)=1;
a(2,3)=6;a(2,5)=1;
a(3,4)=7;a(3,5)=5;a(3,6)=1;a(3,7)=2;
a(4,7)=9;a(5,6)=3;a(5,8)=2;a(5,9)=9;
a(6,7)=4;a(6,9)=6;a(7,9)=3;a(7,10)=1;
a(8,9)=7;a(8,11)=9;
a(9,10)=1;a(9,11)=2;a(10,11)=4;
s=cellstr(strcat('v',int2str([1:11]'))); % 顶点字符串
G=graph(a,s,'Upper'); % 利用邻接矩阵的上三角元素构造无向图
[p,d]=shortestpath(G,1,11); % 求最短路径和最短距离
h = plot(G,'EdgeLabel',G.Edges.Weight); % 画无向图
highlight(h,p,'EdgeColor','r','LineWidth',2); % 最短路径加粗