@lychee123
2017-09-09T23:31:46.000000Z
字数 4605
阅读 1158
图论
是一种求单源最短路的算法
算法中需要用到的主要变量
int n; //表示n个点,从1到n标号
int st,en; //st为源点,en为终点
int dis[N]; //d[i]表示源点st到点i的最短路
int pre[N]; //记录在最短路上的前驱
queue<int>q;
bool vis[N]; //vis[i]=true表示点i在队列中vis[i]=false表示不在队列中
几乎所有的最短路算法其步骤都可以分为两步
1.初始化
2.松弛操作
初始化: dis数组全部赋值为INF(无穷大);
p数组全部赋值为-1,表示没有前驱
dis[st]=0;表示st到st的最短路是0。将源点入队;
(在整个算法中有顶点入队了要记得标记vis数组,有顶点出队了记得消除那个标记)
队列+松弛操作
读取队头顶点u,并将队头顶点u出队(记得消除标记);将与点u相连的所有点v进行松弛操作,如果能更新估计值(即令dis[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(记得标记),如果已经在队列中了,那么就不用入队
以此循环,直到队空为止就完成了单源最短路的求解
SPFA可以处理负权边
定理: 只要最短路径存在,上述SPFA算法必定能求出最小值。
证明:
每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值dis[v]变小。所以算法的执行会使dis越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着dis值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。
判断有无负环:
如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)
输入
输入的第一行为一个整数T(T组测试数据<=100)
对于每一组测试数据,第一行为两个整数 N,M。N表示地图上的景点个数(N<=2000)。(1 . 2 . 3....N),M表示道路个数。接下来是M行,每行包括三个整数 S .T .L,表示 景点S与景点T之间的距离是L(L<=1000)
最后一行是两个整数st 和 en ,表示旅行的 起点 和 目的地。输出
对于每组测试数据,输出包括两行,第一行是从st到en的 最短路径长度。
第二行是输出从起点到目的地所经过的所有景点(如Sample output所示) 以先后顺序输出,数据保证只有一条最优路径
如果不能到达就输出 none!
每组测试数据后输出一个空行。
输入
1
5 8
1 2 1
1 4 10
2 3 5
2 4 1
2 5 12
3 4 1
3 5 1
5 4 8
1 5输出
Case 1 : 4
1 2 4 3 5
#include<bits/stdc++.h>
using namespace std;
const int maxn=2010;
const int inf=1e9+7;
int t,n,m,u,v,l,st,en,i,j,kk;
int num,p[maxn];//p[id]代表结点id存在的最后一条边的编号,即E[p[id]]]这条边
int dis[maxn],pre[maxn];//dis记录st到i的距离,pre记录i的前驱
bool vis[maxn];//表示i点是否在路径中
struct node
{
int v,l,next;
node(){}//默认构造函数,因为下面申明的一个有参构造函数,所以默认构造函数会失效,所以需要再次申明。
node(int v,int l,int next):v(v),l(l),next(next){}//构造函数
}E[maxn*maxn];
void add(int u,int v,int l)
{
E[num]=node(v,l,p[u]);//构造函数才能这样赋值
p[u]=num++;
}
void spfa(int st)
{
for(i=1;i<=n;i++)
dis[i]=inf,vis[i]=false;
pre[st]=-1;//起点没有前驱
dis[st]=0;//起点到起点的距离为0
queue<int>q;
q.push(st);
vis[st]=true;//起点进入队列
int now,next;//now为现在的这个点,next为与之相连的点
while(!q.empty())
{
now=q.front();
q.pop();
vis[now]=false;//取出了now
for(i=p[now];i!=-1;i=E[i].next)//反向找与之相连的点,从最后加入的边开始到最开始,直到遍历完所有的与now相连的点
{
next=E[i].v;//与之相连的点
if(dis[next]>dis[now]+E[i].l)//松弛
{
dis[next]=dis[now]+E[i].l;
pre[next]=now;
if(!vis[next])//当next不在队列里就丢进去,并且标记该点已经在队列里面了
{
q.push(next);
vis[next]=true;
}
}
}
}
}
void out(int x)//递归输出(然而还是懵逼的)
{ //commit by w703710691d: 如果这个点不是起点,那就先输出他前驱及其之前的点,最后在输出这个点
if(pre[x]==-1) //这个点已经是起点了,输出的时候注意下格数。
{
if(x==en)
printf("%d\n",en);
else
printf("%d ",x);
return;
}
out(pre[x]); //不是起点,先输出前面的点
//在输出这个点
if(x==en)
printf("%d\n",en);
else
printf("%d ",x);
}
int main()
{
scanf("%d",&t);
for(kk=1;kk<=t;kk++)
{
num=0;
memset(p,-1,sizeof(p));//一定要记得初始化
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&l);
add(u,v,l);
add(v,u,l);
}
scanf("%d%d",&st,&en);
spfa(st);
if(dis[en]==inf)
{
printf("Case %d : none!\n",kk);
puts("");//换行的意思
}
else
{
printf("Case %d : %d\n",kk,dis[en]);
//out(en);//递归输出
stack<int>s;
for(i=en;pre[i]!=-1;i=pre[i])
{
s.push(i);
}
s.push(st);
while(!s.empty())
{
if(s.top()==st)
printf("%d",s.top());
else if(s.top()==en)
printf(" %d\n",s.top());
else
printf(" %d",s.top());
s.pop();
}
puts("");
}
}
return 0;
}
此题的堆优化Dij写法,堆优化的思想就是把Dij暴力找点的过程,用优先队列代替了,整体复杂度位。写法和spfa差不太多,但是比spfa稳定很多。
#include<bits/stdc++.h>
/* 可以考虑使用pb_ds里面的堆,获取更高性能
#include<ext/pb_ds/priority_queue.hpp>
using namespace __gnu_pbds;
*/
using namespace std;
const int maxn = 2e3 + 5;
const int maxm = maxn * maxn; ///边要开多少,自己掂量吧
struct EDGE
{
int to, next, len;
EDGE() {}
EDGE(int to, int next, int len): to(to), next(next), len(len) {}
} edge[maxm];
int head[maxn], edgecnt;
void init()
{
memset(head, -1, sizeof(head));
edgecnt = 0;
}
void add(int s, int t, int l)
{
edge[edgecnt] = EDGE(t, head[s], l);
head[s] = edgecnt++;
}
int pre[maxn], dis[maxn], vis[maxn]; ///分别位最短路上的前驱,到起点的距离,是否已经在最短路上
struct HeapNode ///优先队列中的元素,由于是大者有限,反着重载之后dis小的先出来
{
int u, dis;
HeapNode(int u, int dis): u(u), dis(dis) {}
bool operator < (const HeapNode &b) const
{
return dis > b.dis;
}
};
void HeapDij(int st) ///堆优化Dij,以st位起点
{
memset(vis, 0, sizeof(vis));
memset(dis, 0x7F, sizeof(dis));
memset(pre, -1, sizeof(pre));
priority_queue<HeapNode> q;
/* pb_ds的堆就应该这样定义了
__gnu_pbds::priority_queue<HeapNode> q;
*/
dis[st] = 0;
q.emplace(st, 0);
/* pb_ds的东西暂时不支持emplace
q.push(HeapNode(st, 0));
*/
while(!q.empty())
{
int u = q.top().u;
q.pop();
if(vis[u]) ///标记,一个点只会更新周围的点一次
continue;
vis[u] = 1;
for(int i = head[u]; ~i; i = edge[i].next)
{
int v = edge[i].to;
if(dis[v] > dis[u] + edge[i].len)
{
pre[v] = u; ///记录前驱
dis[v] = dis[u] + edge[i].len;
q.emplace(v, dis[v]);
/* pb_ds的东西暂时不支持emplace
q.push(HeapNode(v, dis[v]));
*/
}
}
}
}
int main()
{
int T, ks = 0;
scanf("%d", &T);
while(T--)
{
init();
int n, m;
scanf("%d %d", &n, &m);
while(m--)
{
int s, t, l;
scanf("%d %d %d", &s, &t, &l);
add(s, t, l);
add(t, s, l);
}
int st, en;
scanf("%d %d", &st, &en);
HeapDij(st);
printf("Case %d : ", ++ks);
if(!vis[en]) ///简单的判断,没被标记,肯定连图都不连通
puts("none!");
else
{
printf("%d\n", dis[en]);
vector<int> vec;
while(en != -1) ///沿着pre从终点往起点走
{
vec.push_back(en);
en = pre[en];
}
for(int i = vec.size() - 1; i >= 0; i--)
{
printf("%d", vec[i]);
if(i)
printf(" ");
else
printf("\n");
}
}
printf("\n");
}
return 0;
}