dijkstra算法可以用来实现无负权值的最短路求法,bellman_fort算法可用于实现有限制访问次数的最短路问题,spfa算法是最常用来求单源最短路的算法。
Dijkstra堆排序实现:
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;//定义序列对,用于存储距离和结点下标
const int N = 1e6 + 10;
int n, m;
int h[N], w[N], e[N], ne[N], idx;//定义带权值的邻接表需要的数组和索引号
int dist[N];
bool st[N];//用于标记离结点最短距离的相邻接点
void add(int a, int b, int c)//实现邻接表
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;//堆排序实现查找队列中的最短距离的结点
heap.push({0, 1});//入堆第1个结点距离为0
while (heap.size())
{
auto t = heap.top();//堆头用top
heap.pop();
int ver = t.second, distance = t.first;//取堆中距离最短的第一个元素的下标和距离
if (st[ver]) continue;//如果已经标记过了,重复标记没有意义只会重复占用计算资源,所以已经标记了的元素不应该进行
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
heap.push({dist[j], j});
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
cout << dijkstra() << endl;
return 0;
}
作者:yxc
Bellman_fort代码实现:
//这里填你的代码^^
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~
#include<iostream>
#include<algorithm>
#include <cstring>//memset函数的基础库
using namespace std;
const int N = 510,M = 10010;
int n,m,k;
int dist[N];
int backup[N];//存储原始结点的距离,防止串联导致结果出错
struct Edge
{
int x,y,w;
}e[M];//结构体
void bell_fort()//进行边数k限制次循环
{
memset(dist,0x3f,sizeof dist);//初始化
dist[1] = 0;
for(int i=0;i<k;i++)
{
memcpy(backup,dist,sizeof dist);//备份该节点的原始距离
for(int j=0;j<m;j++)//进行所有边的距离更新
{
int a=e[j].x,b = e[j].y,c=e[j].w;
dist[b]=min(dist[b],backup[a]+c);
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i = 0;i < m;i++)
{
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
e[i] = {x,y,w};
}
bell_fort();
if(dist[n] > 0x3f3f3f3f / 2) puts("impossible");
else printf("%d\n",dist[n]);
return 0;
}
作者:ovOr
spfa代码实现:
//使用已经更新过的结点,来更新已经更新过节点的相邻结点
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100010;
int n,m;
int e[N],ne[N],h[N],w[N],idx;
int dist[N];
bool st[N];//存储的是已经更新过的结点
void add(int a,int b,int c)
{
e[idx] = b,w[idx] = c,ne[idx] = h[a], h[a] = idx++;
}
int spfa()
{
memset(dist,0x3f,sizeof dist);
queue<int> q;
dist[1] = 0;
q.push(1);
st[1] = true;
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i=h[t];i!=-1;i=ne[i])
{
int j = e[i];
if(dist[j] > dist[t] + w[i])//有点bellman_fort算法,dist[b] =min(dist[b], backup[a] + w)
{
dist[j] = dist[t] + w[i];
if(!st[j])//若已经标记过了
{
q.push(j);
st[j] = true;
}
}
}
}
return dist[n];
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
for(int i = 0;i < m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
int t = spfa();
if(t == 0x3f3f3f3f) puts("impossible");
else printf("%d\n",t);
}
stl实现:
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int N=100010;
vector<vector<pair<int,int> > >edge(N);
int dist[N];
bool vis[N];
int n,m;
int spfa()
{
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
queue<int>q;
q.push(1);
vis[1]=true;
while(!q.empty())
{
int t=q.front();
q.pop();
vis[t]=false;
for(int i=0;i<edge[t].size();i++)
{
int p=edge[t][i].first;
int len=edge[t][i].second;
if(dist[p]>dist[t]+len)
{
dist[p]=dist[t]+len;
if(!vis[p])
{
q.push(p);
vis[p]=true;
}
}
}
}
if(dist[n]==0x3f3f3f3f)return -1;
return dist[n];
}
int main()
{
cin>>n>>m;
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
edge[a].push_back({b,c});
}
int ans=spfa();
if(ans==-1)puts("impossible");
else printf("%d\n",ans);
return 0;
}
三者的异同:
对负权值:
dijkstra算法不能实现负权值问题的原因是因为,该算法是基于假设图中的边不含负权值边进行求解的,好像一个只会加法而不会减法的算法。其余的两种算法都是可以计算负权值边的,他们依据放缩的办法,能够备份节点的初始值,从而能对任何权值属性的边进行更新。
st[N]数组存储节点的要求:
dijkstra算法st数组中存储的是距离结点最近的节点的堆,spfa算法存储的已经更新了的节点,且可逆,意思就是可以true后false的进行改变,从而实现节点的进队和出队。
有关一些实现的细节问题:
为什么要/2原因是,到达一个节点的距离为无穷大后,若后续有负权值的结点就会导致无穷大变小,所以你对无穷大/2/3都是可以实现的,只要比无穷大小就行。
负权回路的问题
bellman_fort算法由于存在有限条边数的访问设置,所以可以在存在复权回路的图中进行实现,但是spfa一旦存在负权回路就会一直在负权回路中打转,不断添加更新节点直到最后超时结束。但是可以利用上述两项技术检测图中是否存在负权值回路,n个结点最多n-1条边,每次访问一条边就将计数器+1,检测最后的cnt是否>=n,若是最图中一定存在负权回路,原因是存在复权回路就会打转,结果就是负权回路中的结点可能会被访问很多次,单只计数器的结果大于n-1,。