前言
本文所有内容出自于个人的理解,可能有谬误,还请多多包涵,敬请指正。qq :2039316792
本文中参考的所有文章,将会在专页中一一列出。
目录
正文
1、Dijkstra
2、Spfa
概论
Spfa是一种可以处理有负权边的图的算法,也能处理有负权环的图,但是Spfa十分玄学,对于菊花图,网格图等特殊的图,便会退化为Bellman-Ford算法。其思想为动态逼近,写法十分类似BFS 。
基本流程
- 初始化dis[每一个点]=inf,dis[起点]=0。
- 将起点压入队列,标记起点在队列中将起点压入队列,标记起点在队列中
- 取出队首,取消队首的标记,遍历队首可扩展的边,若存在dis[边的另一个点]>dis[队首]+该边边权,那么更新dis。
- 若边的另一个点不在队列中,那么标记改点并将该点压入队列。
- 若此时队列不为空,则继续执行语句3.
PS:若要判负环,则只要在语句4中判断某一个点出现的次数是否大于n+2。(也可以是n+1,个人认为n+2更为稳妥)
时间复杂度
O( KE )
PS:由于原论文中并未涉及关于K的证明,所以K不能当做常数来看待。对于随机图,Spfa的效果较好。
代码实现
PS:以下代码以洛谷 P3371 【模板】单源最短路径(弱化版)为例
#include<bits/stdc++.h>
#define inf 2147483647
#define maxn 100000
using namespace std;
struct edge
{
int nxt,to,w;
}p[maxn*50];
int n,m,s,cnt;
int head[maxn],dis[maxn];
bool vis[maxn];
void add(int u,int v,int w)
{
p[++cnt].nxt=head[u];
p[cnt].to=v;
p[cnt].w=w;
head[u]=cnt;
}
void spfa()
{
queue< int >q;
for(int i=1;i<=n;i++)dis[i]=inf;
dis[s]=0;
vis[s]=1;
q.push(s);
while(!q.empty())
{
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=p[i].nxt)
{
int to=p[i].to;
if(dis[to]>dis[x]+p[i].w)
{
dis[to]=dis[x]+p[i].w;
if(!vis[to])
{
vis[to]=1;
q.push(to);
}
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
spfa();
for(int i=1;i<=n;i++)printf("%d ",dis[i]);
return 0;
}
优化
SLF:Small Label First 策略
设即将压入双端队列的点为是j,队首为i,若dis[j]<dis[i]则将j压入队首,否则压入队尾。
LLL:Large Label Last 策略
设队首为i,即将压入双端队列的点为j,则有
x
=
∑
i
=
1
n
d
i
s
i
n
(
队
列
中
所
有
d
i
s
值
的
平
均
值
)
x=\frac{\sum_{i=1}^n dis_i}{n}(队列中所有dis值的平均值)
x=n∑i=1ndisi(队列中所有dis值的平均值)
若dis[j]>x,则将点j压入队尾。
代码我先欠着。。。。先更新Dijsktra的GIF。。。。
3、Floyd
参考文献
福利
还不更╭(╯^╰)╮