最短路径(SP)问题相关算法与模板

相关概念:

有向图、无向图:有向图的边是双行道,无向图的边是单行道。在处理无向图时,可以把一条无向边看做方向相反的两条有向边。

圈 cycle / 回路 circuit:在相同顶点上开始并结束且长度大于0的通路。

环 loop:起点与终点重合的边。

负圈:含有负权边的圈。

 

 问题类型?是否兼容负圈?时间复杂度?
Bellman-Ford单源O(V·E)
Dijkstra单源×O(E·logV)
Floyd-Warshall任意点对O(V3)

 

 

 

 

 

 

 

1. Bellman-Ford 算法(单源) O(VE)

设d[]存放最短路径,对于每条边(from, to),d[to] = d[from] + cost 一定成立。

由于边在es[]中存储顺序的关系,可能出现计算到 d[to] 时 d[from] 还没出现的情况,此时d[to] 的值会继续保持INF,等到下一次循环再被更新。

当图中存在V个点时,从起点s出发共有V-1条路径,因此外层循环最多执行V-1次就能消除d[]中所有INF,并得到结果。如果图中存在负圈,最短路径会不断减小,外层循环执行次数就会超过V-1,因此只需检查更新次数是否达到V就能判断是否存在负圈。

 1 struct edge{int from,to,cost;};
 2 
 3 edge es[MAX_E];
 4 int d[MAX_V];  //shortest paths
 5 int V,E;  //number of vertices and edges
 6 
 7 bool bellman_ford(int s)
 8 {
 9     for(int i=0;i<V;i++) //vertices are indexed from 0
10         d[i]=INF;
11     d[s]=0;
12     int n=0;
13     for(n=0;n<V;n++){  //to be executed |V|-1 times at most
14         bool update=false;
15         for(int i=0;i<E;i++){
16             edge e=es[i];
17             if(d[e.from]!=INF && d[e.to]>d[e.from]+e.cost){
18                 d[e.to]=d[e.from]+e.cost;
19                 update=true;
20             }
21         }
22         if(!update)
23             break;
24         if(n==V-1)  //negative loops exists
25             return true;26     }
27     return false;
28 }

 

 

2.Dijkstra 算法(单源、无负圈)O(E·logV)

该算法的核心在于从已经确定最短路径的点出发,寻找相邻点的最短路径。

令d[s]=0,先更新s所有邻居的sp,入队,再从s所有邻居开始,更新它们的邻居的sp,以此类推……

借助升序优先队列,优先执行sp值小的点,可以避免内层for循环被不断执行,有效减小时间复杂度。

 1 struct edge{int to,cost;};
 2 typedef pair<int,int> P;  //first:sp  second:termination
 3 
 4 int V,E;
 5 vector<edge> G[MAX_V];  //adjcent list
 6 int d[MAX_V];
 7 
 8 void dijkstra(int s)
 9 {
10     priority_queue<P,vector<P>,greater<P>> que;  //#include <queue>
11     fill(d,d+V+1,INF);
12     d[s]=0;
13     que.push(P(0,s));
14 
15     while(!que.empty()){
16         P p=que.top(); que.pop();
17         int v=p.second;
18         if(d[v]<p.first) continue;  
19         for(int i=0;i<G[v].size();i++){
20             edge e=G[v][i];
21             if(d[e.to]>d[v]+e.cost){
22                 d[e.to]=d[v]+e.cost;
23                 que.push(P(d[e.to],e.to)); //newly updated sp may change the sp of its neighbours
24             }
25         }
26     }
27 }

 计算最短路径条数的方法:

维护数组 int cnt[MAX_V] 并初始化为 cnt[]=0; cnt[s]=1;

if ( d[e.to] > d[v]+e.cost )  cnt[e.to]=cnt[v]

else if ( d[e.to] == d[v]+e.cost )  cnt[e.to]+=cnt[v]

 

3.Floyd-Warshall 算法(任意点间) O(V3)

对于每个点对(i, j ) ,枚举中间点 k。i 到 j 的最短路径取经过中间点 k 和不经过中间点 k 两种情况的结果的最小值。

需要初始化:d[i][i]=0,不存在=INF

int d[MAX_V][MAX_V];  //weight of edges
int V,E;

void floyd_warshall()
{
    for(int k=0;k<V;k++)
        for(int i=0;i<V;i++)
            for(int j=0;j<V;j++)
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}

 

 

参考《挑战程序设计竞赛》(第二版),99-104;离散数学及其应用(中文第七版),595-612

转载于:https://www.cnblogs.com/truelycloud/p/9465506.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单源最短路径问题是指在给定的带权有向图中,找到从源节点到其他所有节点的最短路径。分支限界法可以用来解决单源最短路径问题。 以下是基于Java语言的单源最短路径问题的分支限界算法实现: ```java import java.util.*; public class ShortestPath { private int n; // 图中节点数 private int[][] graph; // 图的邻接矩阵 private int[] dist; // 存储源节点到其他节点的距离 private boolean[] visited; // 标记节点是否已经被访问 // 构造函数 public ShortestPath(int n, int[][] graph) { this.n = n; this.graph = graph; this.dist = new int[n]; this.visited = new boolean[n]; } // 分支限界算法求解单源最短路径 public void shortestPath(int source) { PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a.dist)); // 优先队列,按照节点距离排序 Arrays.fill(dist, Integer.MAX_VALUE); // 初始化距离为正无穷 dist[source] = 0; // 源节点到自身的距离为0 pq.offer(new Node(source, 0)); // 将源节点加入优先队列 while (!pq.isEmpty()) { Node node = pq.poll(); int u = node.u; visited[u] = true; // 标记节点已经被访问 // 遍历u的所有邻居节点 for (int v = 0; v < n; v++) { if (graph[u][v] > 0 && !visited[v]) { // v是u的邻居节点且没有被访问过 int newDist = dist[u] + graph[u][v]; // 计算新的距离 if (newDist < dist[v]) { // 如果新的距离比原来的距离更短,更新距离并加入优先队列 dist[v] = newDist; pq.offer(new Node(v, newDist)); } } } } } // 内部类,表示节点 private static class Node { int u; // 节点编号 int dist; // 节点距离 public Node(int u, int dist) { this.u = u; this.dist = dist; } } // 测试 public static void main(String[] args) { int n = 5; int[][] graph = new int[][] { {0, 10, 0, 30, 100}, {0, 0, 50, 0, 0}, {0, 0, 0, 0, 10}, {0, 0, 20, 0, 60}, {0, 0, 0, 0, 0} }; ShortestPath sp = new ShortestPath(n, graph); sp.shortestPath(0); System.out.println(Arrays.toString(sp.dist)); // 输出源节点到其他节点的最短距离 } } ``` 以上代码实现了分支限界算法求解单源最短路径问题,利用优先队列按照节点距离排序,遍历每个节点的邻居节点,更新距离并加入优先队列。最终输出源节点到其他节点的最短距离。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值