@lychee123
        
        2017-09-09T15:31:46.000000Z
        字数 4605
        阅读 1427
    图论
是一种求单源最短路的算法
算法中需要用到的主要变量
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;//起点到起点的距离为0queue<int>q;q.push(st);vis[st]=true;//起点进入队列int now,next;//now为现在的这个点,next为与之相连的点while(!q.empty()){now=q.front();q.pop();vis[now]=false;//取出了nowfor(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);elseprintf("%d ",x);return;}out(pre[x]); //不是起点,先输出前面的点//在输出这个点if(x==en)printf("%d\n",en);elseprintf("%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());elseprintf(" %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的东西暂时不支持emplaceq.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的东西暂时不支持emplaceq.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(" ");elseprintf("\n");}}printf("\n");}return 0;}