Floyd 算法:
作用:求任意两点最短路
时间:O(V^3)
#define for0(i,a,b) for (int i=a;i<b;i++)
memset(dp,INF,sizeof dp);
for0(i,0,n) dp[i][i] = 0;
int u,v,w;
for0(i,0,m){
scanf("%d %d %d",&u,&v,&w);
dp[u][v] = min(dp[u][v],w);
dp[v][u] = min(dp[v][u],w);
}
for0(k,0,n)
for0(i,0,n)
for0(j,0,n)
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k][j]);
dp[i][j]表示两个点之间可以用1~k-1作为中途点时的最短路径,输入取最小值防止有权值不同的重边
判断有无负环检查是否有dp[i][i]<0,有负环的话就没有最短路径了
堆优化dijkstra 算法:
作用:求单源最短路
时间:O(E*logE)
const int N = 1e5+5;///点的数量
const int M = 5e5+5;///边的数量
struct Edge//储存边
{
int to;
int last;
int w;
}edge[M];
int head[N],id;
void add(int u,int v,int w)//建从u->v,权值为w的边
{
edge[id].to = v;
edge[id].w = w;
edge[id].last = head[u];
head[u] = id++;
}
void init()//建边前的初始化
{
memset(head,0,sizeof head);
id = 1;
}
struct node//储存点
{
int now;
int w;//到达now节点的这条边的权值
bool operator < (const node& a)const{//***比较方式要和自己想的反过来***
return w>a.w;
}
};
bool vis[N];//是否求出最短路径
void dijkstra()
{
memset(vis,0,sizeof vis);
priority_queue<node>que;
int root = 1;//单元最短路径的源点
que.push({root,0});
while (!que.empty()){
node now = que.top();que.pop();
if (vis[now.now]) continue;
vis[now.now] = true;
/*
当前点now记录了这个点到源点的最短距离,问题可以在这儿处理
*/
for (int i=head[now.now];i!=0;i=edge[i].last){
que.push({edge[i].to,edge[i].w+now.w});//***权值记得要加now.w***
}
}
}
不能处理有负权的边的问题,否则当前连到树上的点不一定是最短路径。
跑完dijkstra后相当于建了一棵包含所有节点的树,树上所有点到源点的距离最近。
Bellman_ford 算法
作用:求带负边的单源最短路/判负环
时间:O(V*E)
#define INF 0x3f3f3f3f
const int N = 1e4+5;
const int M = 5e4+5;
struct Edge
{
int to,last,w;
}edge[M];
int id,head[N];
void add(int u,int v,int w)
{
edge[id].to = v;
edge[id].w = w;
edge[id].last = head[u];
head[u] = id++;
}
void init()
{
id = 1;
memset(head,0,sizeof head);
}
int dis[N];
void Bellman_ford(int V)//V表示节点数
{
int root = 1;//若只是判断负环,随便取一个源点即可
memset(dis,INF,sizeof dis);
dis[root] = 1;
for (int k=1;k<V;k++)
for (int u=1;u<=V;u++)
for (int i=head[u];i!=0;i=edge[i].last){
int v = edge[i].to;
if (dis[v]>dis[u]+edge[i].w){
dis[v] = dis[u] + edge[i].w;
/*
若要判断负环,将最外层循环修改为k<=V,并判断k==V时是否还有被修改的点
有则证明有负环,因为有负环会一直更新
*/
}
}
/*
求得的dis[i]即为i到root的最短路径
*/
}
第k次更新dis[i]相当于在更新:i沿着边反向走k步途中能到达的所有点形成的这个图中,i距离源点的最短路径
上一次我们求出了k-1步下每个点的单元最短路径,因此我们用所有能直接一步到达i的节点去更新即可。(也可以说是BFS)
(https://www.cnblogs.com/lfri/p/9521271.html 我讲不清,这篇文章讲的清楚)
当然有些点其实在第k次被更新好了,然后又去更新了别人,这样对那个被更新的点最终单源最短路的结果没有影响,就是可能提前获取了答案。
显然更新完V-1步,所有点的单源最短路都正确了,因为V-1步已经包含整张图了。还能更新说明有负环。
SPFA 算法(队列优化的Bellman_ford)
作用:求单源最短路/判负环
时间:O(V*E) (一般情况远低于)
const int N = 1e4+5;
const int M = 5e4+5;
struct Edge
{
int to,last,w;
}edge[M];
int id,head[N];
void add(int u,int v,int w)
{
edge[id].to = v;
edge[id].w = w;
edge[id].last = head[u];
head[u] = id++;
}
void init()
{
id = 1;
memset(head,0,sizeof head);
}
int val[N];//记录该节点被更新多少次
int dis[N];//记录单源最短路
void SPFA(int V)//V表示节点数
{
memset(dis,INF,sizeof dis);
memset(val,0,sizeof val);
int root = 1;//若只用于判负环,源点可以随便取
bool flag = false;//是否有负环,初始化为无
dis[root] = 0;
/*
下面注释掉的四行是SPFA的优化,节点数多的时候一般来说是会优化...
就是先让dis[]小的去松弛,这样可以让进队列的数更少
*/
//deque<int>que;
//que.pb(root);
queue<int>que;
que.push(root);
while (!que.empty()){
int now = que.front();que.pop_front();
int v;
for (int i=head[now];i!=0;i=edge[i].last){
v = edge[i].to;
if (dis[v]>dis[now]+edge[i].w){
dis[v] = dis[now] + edge[i].w;
//que.pb(v);
//if (dis[que.front()]<dis[que.back()]){que.pf(v);que.pop_back();}
que.push(v);
val[v]++;
if (val[v]==V) {flag = true;break;}
}
}
if (flag) break;
}
}
优化原理是未被松弛成功的点一定不会对下一轮松弛产生影响,因此用队列保留被松弛过的点去更新即可。