floyd/dijkstra/bellmanford/spaf 模板:

1. floyd(不能处理负权环,时间复杂度为O(n^3), 空间复杂度为O(n^2))

floyd算法的本质是dp,用dp[k][i][j]表示以(1....k)为中间点,i, j之间的最短距离为多少,dp[0][i][j]即为原矩阵图。

dp[k][i][j]可以由dp[k-1][i][j]转移得到,即不经过 k 点i, j之间的最短距离为多少,

也可以由dp[k-1][i][k]+dp[k-1][k][j]转移得到,即经过 k 点i, j之间的最短距离为多少。

那么动态转移方程式为:

  dp[k][i][j]=min(dp[k][i][j], dp[k-1][i][k]+dp[k-1][k][j])

很显然实现过程中我们只要开二维数组就可以了,并不需要存储前面那个k的信息,因为k的状态直接就可以由k-1的状态得出。

事实上以上内容也解释了代码中 k 这层循环为什么在最外层。

代码:

 #include <bits/stdc++.h>
#define MAXN 210
using namespace std; const int inf=1e9;
int mp[MAXN][MAXN]; //***记录从i点到j点的最短距离,若不可达则标记为inf
int path[MAXN][MAXN]; //***通过后继节点记录路劲 //***注意这里节点是从0开始计数的
void floyd(int n){
memset(path, -, sizeof(path));
for(int k=; k<n; k++){//***注意最外层循环是 k
for(int i=; i<n; i++){
for(int j=; j<n; j++){
if(mp[i][j]>mp[i][k]+mp[k][j]){
mp[i][j]=mp[i][k]+mp[k][j];
path[i][j]=k; //***将路劲信息通过队列倒序输出即为最短路劲
}
}
}
}
} //***要求输出字典序最小的路劲
/*
void floyd(int n){
memset(path, -1, sizeof(path));
for(int k=0; k<n; k++){//***注意最外层循环是 k
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
if(mp[i][j]>mp[i][k]+mp[k][j]){
mp[i][j]=mp[i][k]+mp[k][j];
path[i][j]=k; //***将路劲信息通过队列倒序输出即为最短路劲
}else if(mp[i][j]==mp[i][k]+mp[k][j]&&path[i][j]>path[i][k]){
path[i][j]=path[i][k]; //***记录字典序最小的路劲
}
}
}
}
}
*/ int main(void){
ios::sync_with_stdio(false), cin.tie(), cout.tie();
int n, m;
while(cin >> n >> m){
for(int i=; i<n; i++){ //***初始化
for(int j=; j<n; j++){
if(i==j){
mp[i][j]=;
}else{
mp[i][j]=inf;
}
}
}
int x, y, z;
while(m--){
cin >> x >> y >> z;
mp[x][y]=mp[y][x]=min(mp[x][y], z);
}
int s, e;
cin >> s >> e;
floyd(n);
if(mp[s][e]>=inf){
cout << "-1" << endl;
}else{
cout << mp[s][e] << endl;
//********下面输出路劲***********
stack<int> st;
int cnt=e;
while(path[s][cnt]!=-){
st.push(cnt);
cnt=path[s][cnt];
}
st.push(cnt);
st.push(s);
while(!st.empty()){
cout << st.top() << " ";
st.pop();
}
cout << endl;
}
}
return ;
}

2. dijkstra(不能有负权边,时间复杂度O(n^2), 空间复杂度O(n^2))

PS: 找了下不能处理负权边的证明,然而网上博客大都写的是不能处理负权环的证明:负环会破坏dijkstra的贪心策略,例如,假设用dijkstra求得图中mp[s][k]的最短距离dist[k]为distancek, 那么此时点k会被标记, 即dist[k]不能再被更新,如果存在一条足够小的负权边,那么s经过这条边到k的距离显然是可以小于distancek的(显然这需要k和连接负权边的点在同一个环中),我们接着用这个错误的dist[k]去更新后面的点,那么得到的答案也就不能保证是正确的咯...

另外,还有一种更简单的例子:假如一张图里有一个总长为负数的环,那么Dijkstra算法有可能会沿着这个环一直绕下去,绕到地老天荒...

对于负权边的证明:

假设一张加权图,有的边长为负数。假设边长最小为-10,我们把所有的边长都加上10,就这就可以得到一张无负值加权图。此时用Dijkstra算法算出一个从节点s到节点t的最短路径L,L共包括n条边,总长为t;那么对于原图,每条边都要减去10,所以原图中L的长度是t-10*n。这是Diskstra算法算出的结果。

那么问题来了:对于加上10之后的图,假设还有一个从s到t的路径M,长度为t1,它共包括n1条边,比L包含的边长多,那么还原回来之后,每条边需要减去10,那么M的总长就是t1-10*n1。那么,是不是M的总长一定比L的总长更长一些呢?不一定。假如n1>n,也就是说M的边数比L的边数更多,那么M减去的要比L减去的更多,那么t1-10*n1<t-10*n是可能的。此时Dijkstra算法是不成立的。

另外,如果一张图里有负数边,但没有总长为负数的环,此时可以用Bellman-Ford算法计算。虽然它比Dijkstra慢了一些,但人家应用范围更广啊。

dijkstra算法和最小生成树的prim有点像,prim算法是将所有点分成两个点集s, w,初始时s中只有一个点,然后依次将w中距离s集合最近的点加入s集合中,直至w为空集..

这两个算法的区别是prim算法中更新的是w点集中的点到s点集的最小距离,dijkstra算法是以s点集中的点为中间节点更新w点集中所有点到出发点的最小距离...

a. 单源最短路代码:

 #include <bits/stdc++.h>
#define MAXN 210
using namespace std; const int inf=0x3f3f3f3f;
int mp[MAXN][MAXN], pre[MAXN], dist[MAXN];
bool vis[MAXN];
//***mp存储图, pre[i]记录i的父亲节点,dist[i]记录源点到 i 的最短距离
//***vis[i]标记节点 i 是否被访问过 int dijkstra(int n, int s, int e){ //***注意节点从0开始
for(int i=; i<n; i++){
dist[i]=mp[s][i];
pre[i]=-;
vis[i]=false;
}
dist[s]=;
vis[s]=true;
for(int i=; i<n; i++){
int min=inf, k=;
for(int j=; j<n; j++){//***更新距离
if(!vis[j]&&dist[j]<min){
min=dist[j];
k=j;
}
}
if(min==inf){
break;
}
vis[k]=true;
for(int j=; j<n; j++){ //***进行松驰操作
if(!vis[j]&&dist[j]>dist[k]+mp[k][j]){
dist[j]=dist[k]+mp[k][j];
pre[i]=k; //***记录路径
}
}
}
return dist[e];
} int main(void){
ios::sync_with_stdio(false), cin.tie(), cout.tie();
int n, m;
while(cin >> n >> m){
for(int i=; i<n; i++){
for(int j=; j<n; j++){
mp[i][j]=mp[j][i]=inf;
}
}
int x, y, z;
while(m--){
cin >> x >> y >> z;
if(mp[x][y]>z){ //***处理重边
mp[x][y]=mp[y][x]=z;
}
}
int s, e;
cin >> s >> e;
int ans=dijkstra(n, s, e);
if(ans>=inf){
cout << - << endl;
}else{
cout << ans << endl;
cout << s << " ";
while(pre[s]!=-){ //***输出路径
cout << pre[s] << " ";
s=pre[s];
}
cout << e << endl;
}
}
return ;
}

b. 存在边权值,求最短路中边权值最小的路径的距离及其权值和

代码:

 const int inf=0x3f3f3f3f;
int mp[MAXN][MAXN], dist[MAXN], val[MAXN];
int cost[MAXN][MAXN];
bool vis[MAXN];
//***mp存储图, pre[i]记录i的父亲节点,dist[i]记录源点到 i 的最短距离,
//***cost[i][j]存储 i 节点到 j 节点的花费, val[i]记录源点到 i 点最短路的最小费用
//***vis[i]标记节点 i 是否被访问过 int dijkstra(int n, int s, int e){ //***注意节点从0开始
for(int i=; i<n; i++){
dist[i]=mp[s][i];
val[i]=cost[s][i];
vis[i]=false;
}
dist[s]=;
vis[s]=true;
for(int i=; i<n; i++){
int MIN=inf, k=;
for(int j=; j<n; j++){//***更新距离
if(!vis[j]&&dist[j]<MIN){
MIN=dist[j];
k=j;
}
}
if(MIN==inf){
break;
}
vis[k]=true;
for(int j=; j<n; j++){ //***进行松驰操作
if(!vis[j]&&dist[j]>dist[k]+mp[k][j]){
dist[j]=dist[k]+mp[k][j];
val[j]=val[k]+cost[k][j];
}else if(!vis[j]&&dist[j]==dist[k]+dist[k][j]){//***取花费小的节点进行松驰
val[j]=min(val[j], val[k]+cost[k][j]);
}
}
}
cout << dist[e] << " " << cost[e] << endl;
}

c. 存在节点权值,求最短路中权值最小的路径的距离及其权值

代码:

 const int INF=0x3f3f3f3f;
int mp[MAXN][MAXN], low[MAXN], tag[MAXN], n, m, rank[MAXN], vis[MAXN];
// low[j]记录出发点到点j的最短距离,tag[j]标记点j是否被选中过, vis[j]记录出发点到点j的最大权值 void dijkstra(int s, int e){
for(int i=; i<n; i++){ //初始化
low[i]=mp[s][i];
}
vis[s]=rank[s];
low[s]=;
for(int i=; i<n; i++){
int MIN=INF;
for(int j=; j<n; j++){
if(low[j]<MIN&&!tag[j]){
MIN=low[j];
s=j;  //s为当前选中的点
}
}
tag[s]=;
for(int j=; j<n; j++){ //更新各点到出发点的最小距离
if(low[j]>mp[s][j]+low[s]){
low[j]=mp[s][j]+low[s];
vis[j]=vis[s]+rank[j];
}else if(low[j]==mp[s][j]+low[s]){ //若距离相等则更新权值更小的点
vis[j]=min(vis[s]+rank[j], vis[j]);
}
}
}
cout << low[e] << " " << vis[e] << endl;
}

d. dijkstra堆优化(时间复杂度O(n*(log*(m), 空间复杂度为n*n)

对于边数远小于n*n的情况其耗时远少于未优化情况

操作:

1. 将与源点相连的点加入堆,并调整堆。
2. 选出堆顶元素u(即代价最小的元素),从堆中删除,并对堆进行调整。
3. 处理与u相邻的,未被访问过的,满足三角不等式的顶点
1):若该点在堆里,更新距离,并调整该元素在堆中的位置。
2):若该点不在堆里,加入堆,更新堆。
4. 若取到的u为终点,结束算法;否则重复步骤2、3。

代码:

 #include <bits/stdc++.h>
#define MAXN 210
using namespace std; vector<pair<int, int> > mp[MAXN];//***记录图
int dist[MAXN];//***记录源点此时到 i 的最短距离
bool vis[MAXN];//***标记该点是否在堆中
const int inf=0x3f3f3f3f; struct node{//***重载比较符使优先队列非升序排列
int point, value;
friend bool operator< (node a, node b){
return a.value>b.value;
}
}; int dijkstra_heap(int s){
priority_queue<node> q;
memset(dist, 0x3f, sizeof(dist));
memset(vis, false, sizeof(vis));
dist[s]=;
q.push({s, dist[s]});
while(!q.empty()){
node u=q.top();
int point=u.point;
q.pop();
if(vis[point]){
continue;
}else{
vis[point]=true;
}
for(int i=; i<mp[point].size(); i++){
int v=mp[point][i].first;
int cost=mp[point][i].second;
if(!vis[v]&&dist[v]>dist[point]+cost){//***松驰操作
dist[v]=dist[point]+cost;
q.push({v, dist[v]});
}
}
}
} int main(void){
ios::sync_with_stdio(false), cin.tie(), cout.tie();
int n, m;
while(cin >> n >> m){
while(m--){
int x, y, z;
cin >> x >> y >> z;
mp[x].push_back({y, z});
mp[y].push_back({x, z});
}
int s, e;
cin >> s >> e;
dijkstra_heap(s);
if(dist[e]>=inf){
cout << - << endl;
}else{
cout << dist[e] << endl;
}
for(int i=; i<n; i++){
mp[i].clear();
}
}
return ;
}

不难发现其代码与之前的写法并没有很大区别,只是将点集s存入优先队列中,这样我们在取dist[k]时只需要取堆顶元素即可,只需O(1)的时间,另外需要log(m)的时间维护优先队列,再加上遍历节点的时间O(n),总共耗时O(n*log(m)).....

3. bellmanford(可以处理负权边情况,并能判断是否存在负权边环.时间复杂度O(n*m), 空间复杂度O(n*n)) 其中m为边数

bellman-ford算法进行n-1次更新(一次更新是指用所有节点进行一次松弛操作)来找到到所有节点的单源最短路。bellman-ford算法和dijkstra其实有点相似,该算法能够保证每更新一次都能确定一个节点的最短路,但与dijkstra不同的是,并不知道是那个节点的最短路被确定了,只是知道比上次多确定一个,这样进行n-1次更新后所有节点的最短路都确定了(源点的距离本来就是确定的)。 
现在来说明为什么每次更新都能多找到一个能确定最短路的节点:

1).将所有节点分为两类:已知最短距离的节点和剩余节点。

2).这两类节点满足这样的性质:已知最短距离的节点的最短距离值都比剩余节点的最短路值小。(这一点也和dijkstra一样)

3).有了上面两点说明,易知到剩余节点的路径一定会经过已知节点

4).而从已知节点连到剩余节点的所有边中的最小的那个边,这条边所更新后的剩余节点就一定是确定的最短距离,从而就多找到了一个能确定最短距离的节点,不用知道它到底是哪个节点。

其判断负权环的机制为:在没有负权环的情况下进行n-1次更新必定能得到所有节点到源点的最短距离,反之则必有负权环(负权环能无限次进行松弛操作嘛)..

代码:

 #include <bits/stdc++.h>
#define MAXN 210
using namespace std; const int inf=0x3f3f3f3f;
int dist[MAXN], pre[MAXN]; //**dist[i]记录此时源点到i的最短距离,pre[i]记录i的前驱节点,即倒序输出为最短路径
struct edge{
int u, v;
int cost;
}mp[MAXN*MAXN*]; //***mp记录所有边及其权值 bool bellman_ford(int n, int m, int s){
memset(dist, 0x3f, sizeof(dist));
dist[s]=;
for(int i=; i<n; i++){//***更新n-1次
int flag=true;
for(int j=; j<m; j++){
if(dist[mp[j].v]>dist[mp[j].u]+mp[j].cost){
dist[mp[j].v]=dist[mp[j].u]+mp[j].cost; //***松驰
pre[mp[j].v]=mp[j].u; //**记录前驱节点
flag=false;
}
}
if(flag){ //***若所有节点都不再更新,则已得到源点到所有节点的最短距离
break;
}
}
for(int j=; j<m; j++){ //***判断是否存在负权环
if(dist[mp[j].v]>dist[mp[j].u]+mp[j].cost){
return false;
}
}
return true;
} int main(void){
ios::sync_with_stdio(false), cin.tie(), cout.tie();
int n, m;
while(cin >> n >> m){
int x, y, z;
for(int i=; i<m; i++){
cin >> x >> y >> z;
mp[i].u=x, mp[i].v=y, mp[i].cost=z;
mp[i+m].u=y, mp[i+m].v=x, mp[i+m].cost=z; //***无向图
}
int s, e;
cin >> s >> e;
bellman_ford(n, m<<, s);
if(dist[e]>=inf){
cout << - << endl;
}else{
cout << dist[e] << endl;
}
}
return ;
}

4. spfa(可以处理负权边并能判断负权环,时间复杂度为O(k*m), 空间复杂度为O(n*n))其中m为边数,k为所有节点的平均进队次数,一般<=2n, k是一个常数,随图的确定而确定. spfa是bellman_ford 的队列优化形式,但其稳定性较差...

代码:

 #include <bits/stdc++.h>
#define MAXN 210
using namespace std; const int inf=0x3f3f3f3f;
bool vis[MAXN]; //***标记点是否在队列中
int cnt[MAXN]; //***cnt[i]记录i节点入队次数,判断是否存在负权环
int dist[MAXN]; //**dist[i]记录此时源点到i的最短距离,pre[i]记录i的前驱节点,即倒序输出为最短路径
vector<pair<int, int> >mp[MAXN]; bool spfa(int n, int s){
memset(vis, false, sizeof(vis));
memset(dist, 0x3f, sizeof(dist));
memset(cnt, , sizeof(cnt));
queue<int> q;
q.push(s);
dist[s]=;
cnt[s]+=;
vis[s]=true;
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=false;
for(int i=; i<mp[u].size(); i++){
int point=mp[u][i].first;
if(dist[point]>dist[u]+mp[u][i].second){ //**松驰操作
dist[point]=dist[u]+mp[u][i].second;
if(!vis[point]){ //***若此点不在队列中则将其入队
vis[point]=true;
q.push(point);
cnt[point]++;
if(cnt[point]>n){ //***判断是否存在负权环
return false;
}
}
}
}
}
return true;
} int main(void){
ios::sync_with_stdio(false), cin.tie(), cout.tie();
int n, m;
while(cin >> n >> m){
int x, y, z;
for(int i=; i<m; i++){
cin >> x >> y >> z;
mp[x].push_back({y, z});
mp[y].push_back({x, z});
}
int s, e;
cin >> s >> e;
spfa(n, s);
if(dist[e]>=inf){
cout << - << endl;
}else{
cout << dist[e] << endl;
}
for(int i=; i<MAXN; i++){ //***多重输入记得情况容器
mp[i].clear();
}
}
return ;
}

最短路(floyd/dijkstra/bellmanford/spaf 模板)的更多相关文章

  1. 几大最短路径算法比较(Floyd & Dijkstra & Bellman-Ford & SPFA)

    几个最短路径算法的比较:Floyd 求多源.无负权边(此处错误?应该可以有负权边)的最短路.用矩阵记录图.时效性较差,时间复杂度O(V^3).       Floyd-Warshall算法(Floyd ...

  2. hdu 2066 ( 最短路) Floyd & Dijkstra & Spfa

    http://acm.hdu.edu.cn/showproblem.php?pid=2066 今天复习了一下最短路和最小生成树,发现居然闹了个大笑话-----我居然一直写的是Floyd,但我自己一直以 ...

  3. 最短路径算法总结(floyd,dijkstra,bellman-ford)

    继续复习数据结构和算法,总结一下求解最短路径的一些算法. 弗洛伊德(floyd)算法 弗洛伊德算法是最容易理解的最短路径算法,可以求图中任意两点间的最短距离,但时间复杂度高达\(O(n^3)\),主要 ...

  4. hdu-2544-最短路(Floyd算法模板)

    题目链接 题意很清晰,入门级题目,适合各种模板,可用dijkstra, floyd, Bellman-ford, spfa Dijkstra链接 Floyd链接 Bellman-Ford链接 SPFA ...

  5. hdu-2544-最短路(Bellman-Ford算法模板)

    题目链接 题意很清晰,入门级题目,适合各种模板,可用dijkstra, floyd, Bellman-ford, spfa Dijkstra链接 Floyd链接 Bellman-Ford链接 SPFA ...

  6. 训练指南 UVA - 11374(最短路Dijkstra + 记录路径 + 模板)

    layout: post title: 训练指南 UVA - 11374(最短路Dijkstra + 记录路径 + 模板) author: "luowentaoaa" catalo ...

  7. 模板C++ 03图论算法 2最短路之全源最短路(Floyd)

    3.2最短路之全源最短路(Floyd) 这个算法用于求所有点对的最短距离.比调用n次SPFA的优点在于代码简单,时间复杂度为O(n^3).[无法计算含有负环的图] 依次扫描每一点(k),并以该点作为中 ...

  8. hdu 2544 单源最短路问题 dijkstra+堆优化模板

    最短路 Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submis ...

  9. 【ACM程序设计】求短路 Floyd算法

    最短路 floyd算法 floyd是一个基于贪心思维和动态规划思维的计算所有点到所有点的最短距离的算法. P57-图-8.Floyd算法_哔哩哔哩_bilibili 对于每个顶点v,和任一顶点对(i, ...

随机推荐

  1. subline 的常用命令

    zsh 配置 编辑zsh 命令 vim .zshrc alias subl="/Applications/Sublime\ Text.app/Contents/SharedSupport/b ...

  2. PHP通过session id 实现session共享和登录验证的代码

    先说说,这个机制的用途吧,到现在为止战地知道这个机制有两个方面的用途: 首先,多服务器共享session问题,这个大家应该都能够理解的,当一个网站的用户量过大,就会使用服务器集群,例如专门有一个登录用 ...

  3. 20170326 ABAP调用外部webservice实例

    调用外部webservice 使用Tcode: SICF SPROXSET      设置创建proxy 的权限 SE80 SOAMANAGER 一.目的:调用外部SRM系统的 webservice: ...

  4. LeetCode:砖墙【554】

    LeetCode:砖墙[554] 题目描述 你的面前有一堵方形的.由多行砖块组成的砖墙. 这些砖块高度相同但是宽度不同.你现在要画一条自顶向下的.穿过最少砖块的垂线. 砖墙由行的列表表示. 每一行都是 ...

  5. 剑指Offer:二叉搜索树的后序遍历序列【33】

    剑指Offer:二叉搜索树的后序遍历序列[33] 题目描述 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. ...

  6. Java基础教程:对象比较排序

    Java基础教程:对象比较排序 转载请标明出处:http://blog.csdn.net/wangtaocsdn/article/details/71500500 有时候需要对对象列表或数组进行排序, ...

  7. TCP服务器端和客户端程序设计【转】

    本文转载自:http://blog.csdn.net/yueguanghaidao/article/details/7035248# 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.实验目的 ...

  8. ubuntu上swift开发学习1

    学习目的:通过构建一个web应用的实践过程来学习swift.会使用到Perfect框架建立一个web应用(Perfect是swift的一个web框架). 这一篇介绍环境搭建 学习资源: 环境搭建:ht ...

  9. oracle数据库如何备份一张表

    --用户名:tms--创建表ts_dictionary的备份create table ts_dictionary_20160715 as select * from ts_dictionary; 补充 ...

  10. struts2标签(转)

    Struts2 标签库讲解   要使用Struts2的标签,只需要在JSP页面添加如下一行定义即可: <%@ taglib prefix="s" uri="/str ...