1、Dijkstra
dijkstra
#include<iostream>
#include<algorithm>
#define maxn 100
using namespace std;
int edge[maxn][maxn];//邻接矩阵
int vis[maxn];//标记数组,如果这个点相邻边已经被松弛过,标记为1,下次遍历到的时候跳过
int dis[maxn];//存储1到i这个点现在的距离
void init(int n)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==j)
edge[i][j]=0;
else
edge[i][j]=1e9;
}
}
}
void dijkstra(int n)
{
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
dis[i]=edge[1][i];
//找到现在距离1最近的点,一共要找n-1次
for(int i=2;i<=n;i++)
{
int minn=1e9;
int pos=-1;
for(int j=2;j<=n;j++)
{
if(vis[j]==1) continue;//松弛过
if(minn>dis[j])
{
pos=j;
minn=dis[j];
}
}
//标记
vis[pos]=1;
//松弛操作
for(int j=2;j<=n;j++)
{
if(!vis[j]&&edge[pos][j]!=1e9)//这条边存在
{
dis[j]=min(dis[j],dis[pos]+edge[pos][j]);
}
}
}
}
int main()
{
int num;
cin>>num;//几个点
int n;//几条边
cin>>n;
init(n);
//输入边的邻接矩阵
int a,b;//边
for(int i=0;i<n;i++)
{
cin>>a>>b;
cin>>edge[a][b];
//edge[b][a]=edge[a][b];
}
dijkstra(num);
int ans=-1;
for(int i=2;i<=num;i++)
{
ans=max(ans,dis[i]);
}
cout<<ans<<endl;
}
bellmanford算法
链接
Dijkstra 算法虽然好,但是他不能解决带有负权边的(边的权值为负数)的图
核心代码:
for(int k = 1 ; k <= n - 1 ; k ++)
{
for(int i = 1 ; i < m ; i ++)
{
if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i] ;
}
}
上边的代码外循环共循环了n - 1 次(n为顶点的个数),内循环共循环了m次(m代表边的个数)即枚举每一条边, dis 数组是的作用和dijkstra 算法一样,也是用来记录源点到其余各个顶点的最短路径,u,v,w 三个数组用来记录边的信息。例如第i条边存储在u[i]、v[i]、w[i]中,表示从顶点u[i]到顶点v[i]这条边(u[i] --> v[i])权值为w[i]。
除此之外,bellman-ford 算法还可以检测出一个图是否含有负权回路。如果进行n-1轮松弛操作之后仍然存在
if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];
的情况,也就是说在进行n-1轮松弛后,仍可以继续成功松弛,那么此图必然存在负权回路。
#include<bits/stdc++.h>
const int INF = 9999999;
using namespace std;
int main()
{
int u[100] , v[100] , w[100] , dis[100] , n , m , ck , flag;
cin>>n>>m;
for(int i = 1 ; i <= m ; i ++)
{
cin>>u[i] >> v[i] >> w[i];
}
for(int i = 1 ; i <= n ; i ++)
dis[i] = INF;
dis[1] = 0;
for(int k = 1 ; k <= n - 1 ; k ++)
{
ck = 0 ; //用来标记本轮松弛操作中数组dis是否会发生更新
for(int i = 1 ; i <= m ; i ++)
{
if(dis[v[i]] > dis[u[i]] + w[i])
{
dis[v[i]] = dis[u[i]] + w[i];
ck = 1 ; //数组dis发生更新,改变check的值
}
}
if(ck == 0)
break; //如果dis数组没有更新,提前退出循环结束算法
}
flag = 0 ;
for(int i = 1 ; i <= m ; i ++)
if(dis[v[i]] > dis[u[i]] + w[i])
flag = 1;
if(flag == 1)
printf("此图包含有负权回路\n");
else
{
for(int i = 1 ; i <= n ; i ++)
printf("%d ",dis[i]);
}
return 0 ;
}
/*
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
*/
SPFA
对bellman-ford的队列优化
模板
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
const int MAXN = 5500;
int n,m,w;
struct Edge
{
int v,w,next;
}edge[MAXN];
int head[MAXN],dis[MAXN],vis[MAXN],t;
void Init()
{
memset(head,-1,sizeof(head));
t = 0;
}
void Add_edge(int u,int v,int w)
{
edge[t].v = v;edge[t].w = w;edge[t].next = head[u];head[u] = t++;
}
bool SPFA()
{
int mark[MAXN];//记录每个点如队列的次数
for(int i = 1;i <= n;i ++)
{
mark[i] = 0;dis[i] = INF;vis[i] = 0;
}
queue<int> q;
q.push(1); //我们只需要判断负环,随便找一个起点就好
dis[1] = 0;
vis[1] = 1;//入队列
mark[1] ++;
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = 0;//出队列
for(int i = head[u];i != -1;i = edge[i].next)
{
int v = edge[i].v;
if(dis[v] > dis[u] + edge[i].w)
{
dis[v] = dis[u] + edge[i].w;
if(!vis[v])//不在队列中的时候出队
{
q.push(v);mark[v] ++;vis[v] = 1;
}
if(mark[v] >= n)//如果不存在负环,那么最多更新n-1次就可以得到最终的答案,因为一次最少更新一个节点,那么如果出现了更新n次,那么就一定出现了负环
return false;
}
}
}
return true;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
Init();
int u,v,z;
scanf("%d%d%d",&n,&m,&w);
for(int i = 0;i < m;i ++)
{
scanf("%d%d%d",&u,&v,&z);
Add_edge(u,v,z);
Add_edge(v,u,z);
}
for(int i = 0;i < w;i ++)
{
scanf("%d%d%d",&u,&v,&z);
Add_edge(u,v,-z);
}
if(!SPFA())
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
floyd
详解
dijsktra是求一个点到别的点,floyd是求每一个点到另一个点。
用一个三重循环,通过遍历每一个结点,对距离矩阵进行更新。是一种动态规划算法。
//Floyd-Warshall算法核心语句
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(e[i][j]>e[i][k]+e[k][j] )
e[i][j]=e[i][k]+e[k][j];