Bellman—Ford算法的时间复杂度比较高,原因在于Bellman—Ford算法要递推n-1次,每次递推,扫描所有的边,在递推n次的过程中很多判断是多余的,SPFA算法是Bellman—Ford算法的一种队列实现,减少了不必要的冗余判断。
大致流程:用一个队列来进行维护。初始时将源点加入队列。每次从队列中取出一个顶点,并与所有与它相邻的顶点进行松弛,若某个相邻的顶点松弛成功,则将其入队。重复这样的过程直到队列为空时算法结束。
实现过程:(1)取出队列头顶点v,扫描从顶点v发出的每条边,设每条边的终点为u,边<v,u>的权值为w,如果dist[v]+w<dist[u],则dist[u]=dist[v]+w,修改path[u]为v,若顶点u不在队列当中,还要将u入队列;如果dist[v]+w<dist[u],则对顶点u不做任何处理。(代码中u为起点,v为终点,这点不同)。
struct node
{
int y,z;
node(int a,int b):y(a),z(b){};
};
while(!q.empty())
{
int st=q.front();
q.pop();
//v[st]=0;
for(int i=0;i<e[st].size();i++)//有多少个有向边出去,邻接表!!!!
{
if( d[st]+e[st][i].z < d[e[st][i].y] )//e[1][0]={2,-3},e[1][1]={5,5}
{
d[e[st][i].y]=d[st]+e[st][i].z;
if(v[ e[st][i].y ]==0)
{
q.push(e[st][i].y);
enqueue_num[e[st][i].y]++;
if (enqueue_num[e[st][i].y] >= n)//判断是否有负环,一个节点入队最多可能是n-1次
{
cout << "Sorry,it have negative circle!\n";
return;
}
v[e[st][i].y]=1;
}
}
}
}
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
#define N 100000+10
using namespace std;
int n,m;
int x,y,z;
bool v[100];
int d[100];
int enqueue_num[100]; //记录入队次数
int vertex_num; //顶点数
struct node
{
int y,z;
node(int a,int b):y(a),z(b){};
};
vector<node> e[100];//邻接表
void spfa(int x)
{
memset(v,0,sizeof(v));
memset(enqueue_num, 0, sizeof(enqueue_num));
d[x]=0;
queue<int>q;
q.push(x);
v[x]=1;//是否在队里
enqueue_num[x]++;//入队次数
while(!q.empty())
{
int st=q.front();
q.pop();
//v[st]=0;
for(int i=0;i<e[st].size();i++)//有多少个有向边
{
if( d[st]+e[st][i].z < d[e[st][i].y] )//e[1][0]={2,-3},e[1][1]={5,5}
{
d[e[st][i].y]=d[st]+e[st][i].z;
if(v[ e[st][i].y ]==0)
{
q.push(e[st][i].y);
enqueue_num[e[st][i].y]++;
if (enqueue_num[e[st][i].y] >= n)//判断是否有负环,一个节点入队最多可能是n-1次
{
cout << "Sorry,it have negative circle!\n";
return;
}
v[e[st][i].y]=1;
}
}
}
}
}
/*
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
0 -3 -1 2 4
5 7
1 2 3
1 3 2
2 4 1
4 1 4
4 3 6
4 5 4
5 3 4
0 3 2 4 8
*/
int main()
{
scanf("%d%d",&n,&m);
memset(d,inf,sizeof(d));
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
e[x].push_back( (node){y,z} );
//e[y].push_back((node){x,z});
//存入无向图
}
spfa(1);
for(int i=1;i<=n;i++)
{
printf("%d ",d[i]);
}
}
// Bellman-Ford算法,计算单点到全部点的最短距离。
for (j = 1; j <= n-1; ++j) //最多循环n-1轮(图退化为链表),这是最大需要松弛的次数
{ // n-1个节点,n-1轮
check = 0; // 用来标记在本轮松弛中数组dis是否发生更新,判断是否还需要循环
for (i = 1; i <= m; ++i)//m条边的松弛算法,每次根据指定的点和边进行松弛算法
{
if (/*dis[u[i]] != INF && */ dis[u[i]] + w[i] < dis[v[i]])// relax
{//需要dis[u[i]] != INF,用来判断前一个点是否已经被访问,如果不被访问就不可能需要去寻找下一个节点的距离
//也可以不需要,dis[u[i]] == INF时是无穷大,无穷大加无穷大还是无穷大,无穷大大于所有值,dis[u[i]] + w[i] < dis[v[i]]不会成立
dis[v[i]] = dis[u[i]] + w[i];
bak[v[i]] = u[i];
check = 1;
}
}
for (i = 1; i <= n; ++i)
{
cout<<i-1<<" "<<dis[i]<<endl;
}
system("pause");
if (check == 0)
{
break;
}
}
// 检测负权回路,若存在,则在对边进行一次遍历后必定会有relax的操作
int flag = 0;
for (i = 1; i <= m; ++i)
{
if (dis[u[i]] + w[i] < dis[v[i]])
{
flag = 1;
}
}
7 10
1 2 6
1 3 5
1 4 5
2 5 -1
3 2 -2
3 5 1
4 3 -2
4 6 -1
5 7 3
6 7 3
这是距离值,单边多次重复进行判断求最小值,每一次都会进行一次松弛算法的实现,使距离值每一次都减小,从无穷大到最小值的实现!!
#include <bits/stdc++.h>
#define INF 1e9
using namespace std;
void DFSPrint(int bak[], int k)
{
if (bak[k] == k)
{
printf("%d ", k);
return;
}
DFSPrint(bak, bak[k]);
printf("%d ", k);
return;
}
/*
7 10
1 2 6
1 3 5
1 4 5
2 5 -1
3 2 -2
3 5 1
4 3 -2
4 6 -1
5 7 3
6 7 3
*/
int main()
{
int i, j, n, m;
int dis[10], bak[10], u[10], v[10], w[10];
int check;
// 读入n和m, n表示顶点个数,m表示边的条数
scanf("%d %d", &n, &m);
// 读入边
for (i = 1; i <= m; ++i)
{
scanf("%d %d %d", &u[i], &v[i], &w[i]);
}
// 初始化bak[]数组,前驱结点均为自己
// 初始化dis[]数组,源点为1号顶点
for (i = 1; i <= n; ++i)
{
bak[i] = i;
dis[i] = INF;
}
dis[1] = 0;
// Bellman-Ford算法,计算单点到全部点的最短距离。
for (j = 1; j <= n-1; ++j) //最多循环n-1轮(图退化为链表),这是最大需要松弛的次数
{ // n-1个节点,n-1轮
check = 0; // 用来标记在本轮松弛中数组dis是否发生更新,判断是否还需要循环
for (i = 1; i <= m; ++i)//m条边的松弛算法,每次根据指定的点和边进行松弛算法
{
if (/*dis[u[i]] != INF && */ dis[u[i]] + w[i] < dis[v[i]])// relax
{//需要dis[u[i]] != INF,用来判断前一个点是否已经被访问,如果不被访问就不可能需要去寻找下一个节点的距离
//也可以不需要,dis[u[i]] == INF时是无穷大,无穷大加无穷大还是无穷大,无穷大大于所有值,dis[u[i]] + w[i] < dis[v[i]]不会成立
dis[v[i]] = dis[u[i]] + w[i];
bak[v[i]] = u[i];
check = 1;
}
}
for (i = 1; i <= n; ++i)
{
cout<<i-1<<" "<<dis[i]<<endl;
}
system("pause");
if (check == 0)
{
break;
}
}
// 检测负权回路,若存在,则在对边进行一次遍历后必定会有relax的操作
int flag = 0;
for (i = 1; i <= m; ++i)
{
if (dis[u[i]] + w[i] < dis[v[i]])
{
flag = 1;
}
}
if (flag)
{
printf("该图有负权回路");
}
else
{
// 输出最终结果
printf("最终结果为:\n");
for (i = 1; i <= n; ++i)
{
printf("1号顶点到%d号顶点的最短距离为:%d\n", i, dis[i]);
}
printf("\n打印1号顶点到5号顶点的最短路径:\n");
DFSPrint(bak, 5);
}
return 0;
}