转载自http://acm.uestc.edu.cn/bbs/read.php?tid=5670
下载ppt帐号:qscqesze
密码:123456
-------------------------------------------------------------------
单源最短路径:
松弛操作:D[i]表示源点s到i的当前最短路径
1.条件:d[i]+e[i][j]<d[j]
2.更新:d[j]=d[i]+e[i][j]
Dijkstra算法:
算法初始时d[s] = 0,其余的点的d[]值为INF。有S、T两个集合,S集合中包含所有已求得最短路的点,T集合包含未求得最短路的点。
当T集合非空时,从T集合中选取d[]值最小的一个点,如果该点的d[] !=INF,则把它放入S集合,此时d[]值就是该点的最短路值。
当T集合为空或从T中选出的点的d[]==INF时,算法结束。
void dijkstra(int s,int t) { 初始化S={空集}, T=全集-S; d[s] = 0; 其余d值为INF while (T非空 && T中最小的d[] != INF) { 取出T中具有最小d[]的点i; for (所有不在S中且与i相邻的点j) if (d[j] > d[i] + cost[i][j]) d[j] = d[i] + cost[i][j]; ( “松弛”操作” ) S = S + {i}; //把i点添加到集合S里 T = T – {i}; } return;}
关于算法的说明:
每次选取的j使d[]最小,这样可以保证它的路径已经是最短路。因为如果经过某个未标记点p到达j会产生更短的路,那么到达j的最短路长度必然要加上一个更大的d [p],矛盾
d [k]=min{d [k],d [j]+cost[j][k]}的作用是在标记j以后更新d数组(松弛操作)
每次循环会对1个点进行标记,所以n-1次循环后所有点都做标记,d[]的含义变成可以经过所有点的最短距离,就是我们要的最短距离
如果找到的d[]最小的一个是d[]=无穷大,则可以提前结束循环,未做标记的点都是不可到达的
Dijkstra只能针对正权
所有边权非负
单源最短路
Dijkstra的本质是基于贪心的思想
反例:
Dijkstra复杂度:
最朴素的实现O(V^2)
堆实现O(ElogV)
Dijkstra堆实现:
用堆实现Dijkstra算法可以将复杂度降至O( ElogV);
用堆来维护T集合中的点,则在选取T集合中具有最小d[]值的点的复杂度为O(1),而每次在堆中维护一个点的复杂度为O(logV),每条边可能进行一次维护。所以总复杂度是O(ElogV);
当E<<V*V时,用堆实现将会快很多。
SPFA算法(Shortest Path Faster Algorithm)
SPFA算法用队列来保存可能做松弛操作的节点。
初始时所有点d[]值置INF,源点d[s]为0。将源点放进队列。
当队列不为空时每次从队列中取出队首节点,对该节点发出的每条边进行松弛。将松弛后d[]值改变并且不在队列中的点加入队列。
void spfa(int s){ for(int i = 1; i <= n; ++i) d[i] = INF; //所有点的d[]置无穷 d[s] = 0; // 源点d[]置0 q.push(s); while(!q.empty()){ int x = q.front(); q.pop(); for all edge(x, y, w) if(d[y] > d[x] + w){ d[y] = d[x] + w; if(y不在q中) { q.push(y); } } } }
示例;
全局最短路径
Floyd-warshell:
求所有点对的最短路径
DP
设d[i][j][k]表示从i到j的路径中,经过的点的编号不超过k的最短路。
边界条件d[i][j][0] = dis[i][j],d[i][i]=0,余下d[i][j]=INF;
转移方程:
d[i][j][k] = Min(d[i][j][k-1] , d[i][k][k-1] + d[k][j][k-1]) (k > 0 , 0 <= i , j <= n)
则dp[i][j][n]即为所求
for(k=1;k<=n;++k) for(i=1;i<=n;++i) for(j=1;j<=n;++j) if(d[i][j]>d[i][k]+d[k][j]) d[i][j]=d[i][k]+d[k][j];
算法时间复杂度为O(n^3)
原程序实际上是这个DP的一个精巧的实现,省略了最后一维数组
因为在第k次进行更新时,只会用d[u][k], d[k][u]对别的 d[][]值进行更新,而d[u][k]和d[k][u]不会改变。
Floyd vs Spfa