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. WebsiteCrawler

    看到网上不少py的爬虫功能极强大,可惜对py了解的不多,以前尝试过使用c# WebHttpRequert类来读取网站的html页面源码,然后通过正则表达式筛选出想要的结果,但现在的网站中,多数使用js ...

  2. smokeping 报警配置

    摘自: http://blog.csdn.net/achejq/article/details/51556494 smokeping 默认用sendmail 发邮件告警,也可以直接调用外部程序进行报警 ...

  3. 分享知识-快乐自己:Hibernate 关联映射

    关联关系映射--概念: 关联关系是使用最多的一种关系,非常重要.在内存中反映为实体关系,映射到DB中为主外键关系. 实体间的关联,即对外键的维护.关联关系的发生,即对外键数据的改变. 外键:外面的主键 ...

  4. JavaScript中函数的无限次运算问题

    开博客有一段时间了,一直没动笔,原因是确实没看到什么值得写的内容.直到今天在司徒正美的博客里看到一个问题. http://www.cnblogs.com/rubylouvre/archive/2012 ...

  5. RPM包构建

    参考资料 https://docs-old.fedoraproject.org/en-US/Fedora_Draft_Documentation/0.1/html-single/RPM_Guide/i ...

  6. 开发工作之外的修炼Live笔记

    “开发工作之外的修炼”这期Live分享了下列话题: [1] 如何发现自己的兴趣 [2] 财富.资源与被动收入 [3] 目标管理 [4] 快速做选择 [5] 时间管理 [6] 如何投资自己 >&g ...

  7. The current .NET SDK does not support targeting .NET Core 2.2

    The current .NET SDK does not support targeting .NET Core 2.2 1. 奇怪的错误 最近遇到了一件奇怪的事, The current .NET ...

  8. snmp++开发实例一

    1.官网下载 snmp开发,首先需要机器已经安装了snmp服务,这方面的资料网上比较完备,安装的时候注意每少一个文件,网上都可以下载到,这样可以自己形成一个包,供以后使用.只要最后snmp的服务开启就 ...

  9. java项目文件的路径问题

    title: 项目下的路径问题 tags: grammar_cjkRuby: true --- 在javaee的项目中,存取文件,解析xml和properties文件,以及项目中的文件,都需要获取路径 ...

  10. .NET&nbsp;下的&nbsp;POP3&nbsp;编程代码共享

    前一段时间在论坛上看见有人问如何使用.net进行POP3编程,其实POP3的使用很简单,所以.net没有向SMTP那样给出相应的类来控制. 废话少说,程序员最需要的使代码. 1.打开VS.NET 20 ...