BFS求无权图的最短路径

用book数组的值表示路径长度即可,省略

Floyd算法(允许负边)

  • Floyd算法可以一次性求出所有节点之间的最短距离,且代码简单,但是时间复杂度达到了n^3,因此只适用于n<200的情况;
  • 原理:任意两点i,j之间的距离分为两种情况:过k点和不过k点。从k=1开始操作遍历到n即可,不过很显然每次计算基本上只有对k的邻边是有效的
  • 代码实现(基于邻接矩阵):
#include<bits/stdc++.h>

using namespace std;

const int INF = 0x3f3f3f3f;
const int MAXN = 105;
int graph[MAXN][MAXN]; void floyd(int n)
{
int s=1;//求s到n的距离
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
if(graph[i][k] != INF)//如果相等则说明无需遍历
for(int j=1; j<=n; j++)
if(graph[i][j] > graph[i][k] + graph[k][j])
graph[i][j] = graph[i][k] + graph[k][j];
printf("%d\n",graph[s][n]);//输出结果
return ;
} int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
if(n==0 && m==0) break;
memset(graph,0x3f,sizeof(graph));
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
graph[a][b] = graph[b][a] = c;
}
floyd(n);
}
return 0;
}
  • Floyd算法适用于邻接矩阵:由于算法是动态规划的思想,必须有一个二维数组表示点与点之间的距离,所以使用其他图的表示方法会浪费空间;
  • 判断负圈:通过代码不难发现,点 i 到自己的距离并非为0,而是graph[i][i] = graph[i][k] + graph[k][i],即绕外面一圈再回来。一旦存在graph[i][i]<0,就说明有负圈存在;

Bellman-Ford算法(允许负边)

  • Bellman-Ford算法解决的是单源最短路径问题,即起点s到图中每个点的最短距离;
  • 原理:每一轮更新中,对于每一个点,询问它的邻居是否可以到达s点:如果可以,当前的点就可以通过邻居到达s点。不难想象,每一轮更新中至少有一个点到s的最短距离可以被确定下来,所以一共需要更新n轮,时间复杂度为O(n*m);
  • 图的表示方式:如果使用邻接矩阵,遍历边的过程依然为O(n*n),并没有得到优化,因此使用数组存边或者邻接表来存图;
  • 代码实现:
#include<bits/stdc++.h>

using namespace std;

const int INF = 0x3f3f3f3f;
const int MAXN = 5e3 + 10;
struct edge{int u, w, v; } e[MAXN*2];
int pre[MAXN],d[MAXN];//pre存放前置节点,d[i]存放s到点i的距离 void Print_path(int s, int t)//打印s到t的最短路径
{
if(s == t) printf("%d",s);
else
{
Print_path(s,pre[t]);
printf(" %d",t);
}
} void bellman(int n, int cnt)
{
int s = 1;//s为起点
memset(d,0x3f,sizeof(d));//初始化为最大值
d[s] = 0;//s到自己的距离为0
for(int k=1; k<=n; k++)
for(int i=0; i<cnt; i++)
{
int x = e[i].u, y = e[i].v;
if(d[x] > d[y] + e[i].w)
{
d[x] = d[y] + e[i].w;
pre[x] = y;
}
}
printf("%d\n",d[n]);//打印s到n的最短距离
Print_path(s,n);
printf("\n");
} int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
int cnt = 0;
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
e[cnt].u = a; e[cnt].v = b; e[cnt].w = c; cnt++;
e[cnt].v = a; e[cnt].u = b; e[cnt].w = c; cnt++;
}
bellman(n,cnt);
}
return 0;
}
  • 判断负圈:很明显当负圈存在时,程序会一直存在距离更新,因此要判断循环次数是否超过了n,如果超过说明有负圈;
  • 优化后的代码:
bool bellman(int n, int cnt)
{
int s = 1;//s为起点
memset(d,0x3f,sizeof(d));//初始化为最大值
d[s] = 0;//s到自己的距离为0
bool updata = true;//updata表示上一轮有没有更新,如果有则继续更新,否则停止更新
int k = 0;//k表示循环次数
while(updata)
{
updata = false;//当前一轮还未进行过更新
k++;
if(k>n) return false;//循环次数超过n则返回有负圈
for(int i=0; i<cnt; i++)
{
int x = e[i].u, y = e[i].v;
if(d[x] > d[y] + e[i].w)
{
updata = true;//发生了更新操作
d[x] = d[y] + e[i].w;
pre[x] = y;
}
}
}
printf("%d\n",d[n]);//打印s到n的最短距离
Print_path(s,n);
printf("\n");
return true;
}

SPFA算法(允许负边)

  • SPFA是对Bellman-Ford算法的优化。在Bellman-Floyd算法每一轮的更新中,如何确定当前节点v需不需要更新?很明显,当且仅当v的邻居节点的最短路径发生变动的时候,节点v才需要更新。SPFA算法使用BFS的思想,把需要更新的节点放进队列中,当队列为空时,算法结束。
  • 判断负圈:节点每进入一次队列即为当前节点更新了一次,由Bellman-Ford算法中的结论可知,当存在一个节点更新次数超过n次时,说明一定有负圈。
  • 代码实现(:
//基于邻接表
#include<bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 10; struct edge
{
int to,w;
}; vector<edge>e[MAXN];
int pre[MAXN];//记录路径
bool inq[MAXN];//是否在队列内,优化用
int Neg[MAXN];//记录循环次数判断负圈
int dis[MAXN];//记录最短距离 void print_path(int s, int t)//递归输出最短路径
{
if(s == t) {printf("%d",s); return ;}
print_path(s, pre[t]);
printf(" %d",t);
} void print_path2(int s, int t)//非递归的路径输出,适用于极端情况
{
stack<int>ans;
while(s!=t)
{
ans.push(t);
t = pre[t];
}
printf("%d",s);
while(!ans.empty())
{
printf(" %d",ans.top());
ans.pop();
}
printf("\n");
} bool spfa(int s, int n)
{
memset(dis,0x3f,sizeof(dis));
memset(inq,false,sizeof(inq));
memset(Neg,0,sizeof(Neg));
Neg[s] = 1;
inq[s] = true;
dis[s] = 0;
queue<int>Q;
Q.push(s);
while(!Q.empty())
{
int u = Q.front();
Q.pop();
inq[u] = false;
for(int i=0; i<e[u].size(); i++)
{
int v = e[u][i].to;
int w = e[u][i].w;
if(dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
pre[v] = u;
if(!inq[v])
{
inq[v] = true;
Q.push(v);
Neg[v]++;
if(Neg[v] > n) return false;//返回值为false代表有负圈
} }
}
}
printf("%d\n",dis[n]);
print_path2(s,n);
return true;
} int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
for(int i=0; i<m; i++)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
edge t = {b,c};
e[a].push_back(t);
}
spfa(1,n);
}
return 0;
}
//基于链式前向星的SPFA算法
#include<bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f;
const int NUM = 1e6 + 10; struct Edge
{
int to,next,w;
}edge[NUM];
int cnt;
int head[NUM];
int dis[NUM];
bool inq[NUM];
int Neg[NUM];
int pre[NUM]; void print_path(int s,int t)//打印起点s到t的最短路径
{
stack<int>ans;
while(t!=s)
{
ans.push(t);
t = pre[t];
}
printf("%d",s);
while(!ans.empty())
{
printf(" %d",ans.top());
ans.pop();
}
printf("\n");
} void init()//前向星的初始化
{
for(int i=0; i<NUM; i++)
{
edge[i].next = -1;
head[i] = -1;
}
cnt = 0;
} void addedge(int u, int v, int w)//前向星的加边操作
{
edge[cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt++;
} bool spfa(int s, int n)
{
memset(inq,false,sizeof(inq));
memset(dis,0x3f,sizeof(dis));
memset(Neg,0,sizeof(Neg));
Neg[s] = 1;
dis[s] = 0;
inq[s] = true;
queue<int>Q;
Q.push(s);
while(!Q.empty())
{
int u = Q.front(); Q.pop();
inq[u] = false;
for(int i=head[u]; i!=-1; i=edge[i].next)
{
int v = edge[i].to; int w = edge[i].w;
if(dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
pre[v] = u;
if(!inq[v])
{
Q.push(v);
inq[v] = true;
Neg[v]++;
if(Neg[v] > n) return false;
}
}
}
}
printf("%d\n",dis[n]);
print_path(s, n);
return true;
} int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
init();
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
addedge(a, b, c);
}
spfa(1, n);
}
return 0;
}

Dijkstra算法(无法求负边)

  • Dijkstra算法应用了贪心的思想,即从起点开始抄近路走。类似于多米诺骨牌,即从起点开始推倒骨牌,当节点第一次被到达时最短路径被确定。
  • 代码借助STL中的优先队列完成,每次取出到S距离最短的节点来模拟多米诺骨牌模型。
//基于邻接表
#include<bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f;
const int NUM = 1e5; struct edge
{
int from, to, w;
edge(int a, int b, int c)
{
from = a;
to = b;
w = c;
}
};
vector<edge>e[NUM]; struct s_node
{
int id, n_dis;
s_node(int b, int c)
{
id = b;
n_dis = c;
}
bool operator < (const s_node & a) const
{
return n_dis > a.n_dis;
}
};
int dis[NUM];
bool done[NUM]; int pre[NUM];//记录前驱结点
void print_path(int s, int t)
{
stack<int>ans;
while(s != t)
{
ans.push(t);
t = pre[t];
}
printf("%d",s);
while(!ans.empty())
{
printf(" %d",ans.top());
ans.pop();
}
printf("\n");
} void dijkstra(int s,int n)
{
memset(dis, 0x3f, sizeof(dis));
memset(done, false, sizeof(done));
dis[s] = 0;
priority_queue<s_node>Q;
Q.push(s_node(s, dis[s]));
while(!Q.empty())
{
s_node u = Q.top();
Q.pop();
if(done[u.id])
continue;
done[u.id] = true;
for(int i=0; i<e[u.id].size(); i++)
{
edge y = e[u.id][i];
if(done[y.to])
continue;
if(dis[y.to] > y.w + u.n_dis)
{
dis[y.to] = y.w + u.n_dis;
Q.push(s_node(y.to, dis[y.to]));
pre[y.to] = u.id;
}
}
}
printf("%d\n",dis[n]);
print_path(s, n);
} int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
if(n==0 && m==0) break;
for(int i=1; i<=n; i++)
e[i].clear();
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
e[a].push_back(edge(a,b,c));
e[b].push_back(edge(b,a,c));
}
dijkstra(1, n);
}
return 0;
}

【C/C++】最短路径的更多相关文章

  1. Johnson 全源最短路径算法

    解决单源最短路径问题(Single Source Shortest Paths Problem)的算法包括: Dijkstra 单源最短路径算法:时间复杂度为 O(E + VlogV),要求权值非负: ...

  2. Floyd-Warshall 全源最短路径算法

    Floyd-Warshall 算法采用动态规划方案来解决在一个有向图 G = (V, E) 上每对顶点间的最短路径问题,即全源最短路径问题(All-Pairs Shortest Paths Probl ...

  3. Dijkstra 单源最短路径算法

    Dijkstra 算法是一种用于计算带权有向图中单源最短路径(SSSP:Single-Source Shortest Path)的算法,由计算机科学家 Edsger Dijkstra 于 1956 年 ...

  4. Bellman-Ford 单源最短路径算法

    Bellman-Ford 算法是一种用于计算带权有向图中单源最短路径(SSSP:Single-Source Shortest Path)的算法.该算法由 Richard Bellman 和 Leste ...

  5. 最短路径算法-Dijkstra

    Dijkstra是解决单源最短路径的一般方法,属于一种贪婪算法. 所谓单源最短路径是指在一个赋权有向图中,从某一点出发,到另一点的最短路径. 以python代码为例,实现Dijkstra算法 1.数据 ...

  6. bzoj 4016: [FJOI2014]最短路径树问题

    bzoj4016 最短路路径问题 Time Limit: 5 Sec Memory Limit: 512 MB Description 给一个包含n个点,m条边的无向连通图.从顶点1出发,往其余所有点 ...

  7. 51nod 1459 迷宫游戏 (最短路径—Dijkstra算法)

    题目链接 中文题,迪杰斯特拉最短路径算法模板题. #include<stdio.h> #include<string.h> #define INF 0x3f3f3f3f ],v ...

  8. C++迪杰斯特拉算法求最短路径

    一:算法历史 迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法.是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题.迪杰斯特拉算法主要特点是以 ...

  9. 求两点之间最短路径-Dijkstra算法

     Dijkstra算法 1.定义概览 Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径.主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止.D ...

  10. 最短路径之Floyd算法

    Floyd算法又称弗洛伊德算法,也叫做Floyd's algorithm,Roy–Warshall algorithm,Roy–Floyd algorithm, WFI algorithm. Floy ...

随机推荐

  1. python爬虫步骤 (新手备学 )爬虫编程。

    Python爬虫是用Python编程语言实现的网络爬虫,主要用于网络数据的抓取和处理,相比于其他语言,Python是一门非常适合开发网络爬虫的编程语言,大量内置包,可以C Python爬虫可以做的事情 ...

  2. python-21-生成器又是什么东西?

    前言 生成器,只要含有yield关键字的函数都是生成器函数,但yield不能和return共用且需要写在函数内. 生成器,是返回一个迭代器的函数,说白了生成器也是迭代器. 一.生成器简介 1.只要含有 ...

  3. 使用placeholder属性设置input文本框的提示信息

    input文本框中设置提示信息,可以使用placeholder属性 <!DOCTYPE html> <html> <head> <meta charset=& ...

  4. Eclipse中Git图标表示内容

    Eclipse中->属性->Team->Git->Label Decorations

  5. 计蒜客 UCF 2015

    #A.Find the twins # 题意 找出每个序列是否有特定的值 # 题解 坑,原始序列输出的时候每一行最后一个不能有空格 #include<bits/stdc++.h> #def ...

  6. JavaDay11

    Java learning_Day11 本人学习视频用的是马士兵的,也在这里献上 <链接:https://pan.baidu.com/s/1qKNGJNh0GgvlJnitTJGqgA> ...

  7. 安装canvas

    本方法仅适用用于window系统 安装canvas需要当前工作环境拥有python环境,且只能适用python2.7版本,v3.x.x版本会造成系统报错 1.在管理员权限下 使用choco insta ...

  8. webserver代理生成本地类的两种方式

    方式1,把webservers地址请求出来的xml拷贝出来放到文本里面后缀改成wsdl文件在VS里面使用下列命令 C:\Program Files (x86)\Microsoft SDKs\Windo ...

  9. 解决Mac无法写入U盘问题

    注:本文出自博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 本文源链接:https://www.cnblogs.com/chloneda/p/upan-to- ...

  10. C# 中indexOf、lastIndexOf、subString方法的理解

    一.indexOf() indexOf("\\"):返回"\\"字符在此实例中第一个出现的索引位置,实例的下标是从0开始,如果未找到则返回-1. indexOf ...