关于SLF优化
朴素SPFA使用常规队列(FIFO)更新距离,并没有考虑优化出队顺序(dis
值小的优先出队)可以在一开始就把各个点的dis
值限值小,从而避免大量的松弛操作,从而提高效率。这就是SLF(Small Label First)
。
实现方式很简单,常规队列替换为双端队列(deque
),对于一个要加入的点u
,如果dis[u]<dis[Q.front()]
,u
就放入队首,否则放入队尾。
此外还有LLL(Large Label first)
优化,思路是对每个要出队的元素u
,比较dis[u]
和队列中所有dis值的平均值,如果dis[u]
大,那么将它弹出放到队尾,取队首元素再重复判断,直达存在dis[u]
小于平均值。发现没有,LLL
和SLF
的目的是一样的。
当然,可以把这两种优化组合起来。
STL双端队列
- 全称:“double-ended queue”,deque是双端数组,而vector是单端的
- 在操作方式上和vector相似
- 可以随机存取元素(用
[]
操作符或at()
方法) - 头部和尾部添加或移除元素非常快,在中部插入或删除元素比较慢
- 需要添加头文件
<deque>
//定义
deque<int> dq1; //一个存放int的deque容器
deque<double> dq2; //一个存放float的deque容器
deque<string> de3; //一个存放string的deque容器。
//添加移除
dq1.push_back(elem); //在容器尾部添加一个元素
dq1.push_front(elem); //在容器头部插入一个元素
dq1.pop_back(); //删除容器最后一个元素
dq1.pop_front(); //删除容器第一个元素
//存取
cout<<dq1.at(pos); //返回pos位置的元素,如果pos越界,抛出out_of_range异常
cout<<dq1[pos]; //返回pos位置的元素,如果pos越界,一般情况下RE
dq1[pos]=1;
cout<<dq1.front(); //返回第一个元素
cout<<dq1.back(); //返回最后一个元素
//迭代器
dq1.begin(); //返回容器中第一个元素的迭代器
dq1.end(); //返回容器中最后一个元素之后的迭代器
dq1.rbegin(); //返回容器中倒数第一个元素的迭代器
dq1.rend(); //返回容器中倒数最后一个元素之后的迭代器
//大小
dq1.size(); //返回容器中元素的个数
dq1.empty(); //判断容器是否为空
dq1.resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
dq1.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
//带参数构造
deque<int> dqx(dq1.begin(), dq1.end()); //用dq1 [beg, end)区间中的元素初始化dqx
deque<int> dqy(n, elem); //用n个elem初始化dqx
deque<int> dqz(dq1); //拷贝构造函数,用dq1初始化dqz
//赋值
deque<int> dqm, dqn;
dqm.assign(dq1.begin(), dq1.end()); //将dq1 [beg, end)区间中的数据拷贝给dqm
dqn.assign(n, elem); //将n个elem拷贝赋值给dqn
dqn=dqm; //整体拷贝
dqn.swap(de1); //整体交换
//插入
dq1.insert(pos, elem); //pos位置插入elem
dq1.insert(pos, n, elem); //pos位置插入n个elem
dq1.insert(pos, beg, end); //pos位置插入迭代器[begin, end)之间的元素
//删除
dq1.clear(); //清空
dq1.erase(beg, end); //删除[beg,end)区间的元素,返回下一个元素的位置
dq1.erase(pos); //删除pos位置的元素,返回下一个元素的位置
//SPFA+SLF
#include<iostream>
#include<cstdio>
#include<cstring>
#include<deque>
using namespace std;
const int maxn=1e4+5, maxm=5e5+5, INF=2147483647;
int dis[maxn], v[maxn], n, m, s;
struct edge{
int t, w; edge *nxt;
edge(int to, int len, edge *next){ t=to, w=len, nxt=next; }
};
edge *h[maxn];
void add(int x, int y, int z){ h[x]=new edge(y, z, h[x]); }
void SPFA(int s)
{
for(int i=1; i<=n; i++) dis[i]=(i==s) ? 0 : INF;
deque<int> Q; //Q为双端队列
Q.push_back(s), v[s]=1;
while(!Q.empty())
{
int x=Q.front(); Q.pop_front(); v[x]=false; //v数组用以标记点i是否在队列中
for(edge *p=h[x]; p; p=p->nxt)
if(dis[p->t]>dis[x]+p->w)
{
dis[p->t]=dis[x]+p->w;
if(!v[p->t])
{
v[p->t]=1;
if(Q.empty() || dis[p->t]>dis[Q.front()]) Q.push_back(p->t);
else Q.push_front(p->t);
}
}
}
}
int main()
{
scanf("%d%d%d", &n, &m, &s);
for(int i=1, x, y, z; i<=m; i++) scanf("%d%d%d", &x, &y, &z), add(x,y,z);
SPFA(s);
for(int i=1; i<=n; i++) printf("%d ",dis[i]);
return 0;
}
//SPFA+SLF+LLL
#include<iostream>
#include<cstdio>
#include<cstring>
#include<deque>
using namespace std;
const int maxn=1e4+5, maxm=5e5+5, INF=2147483647;
int dis[maxn], v[maxn], n, m, s;
struct edge{
int t, w; edge *nxt;
edge(int to, int len, edge *next){ t=to, w=len, nxt=next; }
};
edge *h[maxn];
void add(int x, int y, int z){ h[x]=new edge(y, z, h[x]); }
void SPFA(int s)
{
int cnt=0, sum=0; //cnt为队列中元素个数,sum为队列中dis值总和
for(int i=1; i<=n; i++) dis[i]=(i==s) ? 0 : INF;
deque<int> Q; //Q为双端队列
Q.push_back(s), v[s]=1, cnt=1;
while(!Q.empty())
{
int x=Q.front();
while(cnt*dis[x]>sum) //取出来,放后去,直到找到一个比平均值小的点
{
Q.pop_front();
Q.push_back(x);
x=Q.front();
}
Q.pop_front();
cnt--, sum-=dis[x], v[x]=false; //出队后,更新队列大小及队列dis值之和
for(edge *p=h[x]; p; p=p->nxt)
if(dis[p->t]>dis[x]+p->w)
{
dis[p->t]=dis[x]+p->w;
if(!v[p->t])
{
v[p->t]=1;
if(Q.empty() || dis[p->t]>dis[Q.front()]) Q.push_back(p->t);
else Q.push_front(p->t);
cnt++, sum+=dis[p->t]; //入队后,更新队列大小及队列dis值之和
}
}
}
}
int main()
{
scanf("%d%d%d", &n, &m, &s);
for(int i=1, x, y, z; i<=m; i++) scanf("%d%d%d", &x, &y, &z), add(x,y,z);
SPFA(s);
for(int i=1; i<=n; i++) printf("%d ",dis[i]);
return 0;
}