zoukankan      html  css  js  c++  java
  • K短路的几种求法

    K短路

    引入

    对于最短路,我们可以用Dijstra,Spfa,Floyd m Dijstra,Spfa,Floyd等算法求出。

    那么对于第KK短的路,我们该如何求取呢?

    Ps. 这里都是在有向图上求取KK短路,无向图上可以将其建为双向边然后跑有向图上的KK短路即可。


    算法一:搜索

    我们可以在最短路算法上,轻微改动一下。我们将每一条从起点SS到终点TT的路径搜出来,排序后选取第KK个即可。复杂度为O(w(n+m)+wlogw)O(w(n+m)+wlogw)n,mn,m为点数和边数,ww为路径总条数。

    显然,这样暴力的算法只可能拿到非常少的分。

    算法二:搜索剪枝

    我们发现,当你搜索的路径数已经达到KK条后,凡是已经大于你所搜过的最大的路径长度的情况可以统统剪掉,将较小的加入后,我们弹出最大的一条,并将剪枝用的最大值修改成新的最大值,这个最大值肯定是单调递减的,所以可以大大减少搜索量。

    但是,对于特殊构造的图,仍然如同暴力。

    算法三:启发式搜索

    启发式搜索,就是常用的AA^*算法。

    同样的还是暴力搜索,我们可以对当前的局势进行预估判断,从而达到剪枝的最大化。

    所以这里我们设置两个函数ggffff表示你当前走到这里,然后走到终点至少还总共会走多少距离;gg则表示你当前走了多少距离。

    所以这里我们要先将图反建(边的方向变反),然后从TT跑出单源最短路,TT到点ii的最短路记为dt[i]dt[i],预处理了这个我们就能对当前局势进行预判了。

    当前的函数gg的值就等于走来的点的gg'值加上这条边的权值:
    g[v]=g[u]+dis[u][v]g[v]=g[u]+dis[u][v]

    而当前的预判函数ff的值,当然就是当前已经确定的距离加上我们预期能走的最短距离,预期最短距离已经预处理了,所以可以得知:
    f[v]=g[v]+dt[v]f[v]=g[v]+dt[v]

    然后我们利用前面普通的剪枝方式,按照ff为第一关键字,gg为第二关键字,将状态排序,每次找最小的出来更新,那么刚好更新到第KK个的时候,它就是第KK短的啦。

    这里我们就用bfsbfs(宽度优先搜索)加上优先队列就可以做到了。

    复杂度最好为O(K+mlogm)O(K+mlogm),最坏的话和暴力差不多,所以只适合没有特别精心构造的图。

    下面上博主的简陋代码QWQ:

    poj2449 AC
    #include<queue>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int N=1010,M=1e5+10;
    const int inf=0x3f3f3f3f;
    int n,m,s,t,k;
    struct ss{
    	int to,last,val;
    	#define F to
    	#define G last
    	#define id val
    	ss(){}
    	ss(int a,int b,int c):to(a),last(b),val(c){}
    };
    struct Graphy{
    	ss g[M];
    	int head[M],cnt;
    	void add(int a,int b,int c){
    		g[++cnt]=ss(b,head[a],c);head[a]=cnt;
    	}
    	void clear(){
    		memset(head,0,sizeof(head));cnt=0;
    	}
    };
    namespace Gra_Inv{
    	Graphy G;
    	int que[M],p,q;
    	bool vis[M];
    	bool spfa(int st,int en,int *dis){
    		memset(dis,0x3f,sizeof(int)*(n+1));
    		dis[en]=0;que[p=q=0]=en;vis[en]=1;
    		int *head=G.head;ss *g=G.g;
    		for(;p<=q;p++){
    			int a=que[p%M],v;
    			for(int i=head[a];i;i=g[i].last){
    				v=g[i].to;
    				if(dis[v]>dis[a]+g[i].val){
    					dis[v]=dis[a]+g[i].val;
    					if(!vis[v]){
    						vis[v]=1;
    						que[++q%M]=v;
    						if(dis[que[(p+1)%M]]>dis[que[q%M]]){
    							swap(que[(p+1)%M],que[q%M]);
    						}
    					}
    				}
    			}
    			vis[a]=0;
    		}
    		return dis[st]!=inf;
    	}
    }
    namespace Gra_Astar{
    	Graphy G;
    	struct node{
    		ss s;
    		node(){}
    		node(ss a):s(a){}
    		bool operator <(const node &a)const
    		{return s.F>a.s.F||(s.F==a.s.F&&s.G>a.s.G);}
    	};
    	priority_queue <node> Q;
    	int dis[M];
    	int A_star(int st,int en,int k){
    		int *head=G.head;ss *g=G.g;
    		node tmp=node(ss(dis[st],0,st)),now;
    		Q.push(tmp);
    		if(st==en)++k;
    		while(!Q.empty()){
    			tmp=Q.top();Q.pop();
    			int a=tmp.s.id,v;
    			if(a==en) {--k;if(!k)return tmp.s.F;}
    			for(int i=head[a];i;i=g[i].last){
    				v=g[i].to;
    				now.s.G=tmp.s.G+g[i].val;
    				now.s.F=now.s.G+dis[v];
    				now.s.id=v;
    				Q.push(now);
    			}
    		}
    		return -1;
    	}
    }
    int a,b,c;
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;i++){
    		scanf("%d%d%d",&a,&b,&c);
    		Gra_Inv::G.add(b,a,c);
    		Gra_Astar::G.add(a,b,c);
    	}
    	scanf("%d%d%d",&s,&t,&k);
    	if(Gra_Inv::spfa(s,t,Gra_Astar::dis)){
    		printf("%d
    ",Gra_Astar::A_star(s,t,k));
    	}else puts("-1");
    	return 0;
    }
    

    最短路树加可持久化堆

    这个是在AA^*算法上和最短路算法上,总结升华提取出来的一个较为稳定的KK短路算法。

    我们同样的按照AA^*KK短路的思路,我们反向建图,然后跑TT的单源最短路,我们可以发现,这个跑出来的n1n-1条最短路上的边,和nn个点一定构成一棵树。

    因为树上的两两点对之间的路径是唯一的(路径为最短路),且这个树的根为TT

    那么我们考虑没有在这颗树上的边(称为非树边),如果我们选择了一条uvu ightarrow v的非树边,那么它一定会使得最短路变化(大多数情况下是变大),而变化的值为dis[v]+not_tree_edge[u][v]dis[u]dis[v]+not\_tree\_edge[u][v]-dis[u](其中dis[v]dis[v]表示vvTT的最短路长度)。

    所以我们可以将所有的非树边的边权重新设置为dis[v]+not_tree_edge[u][v]dis[u]dis[v]+not\_tree\_edge[u][v]-dis[u],那么第KK短路肯定为STS ightarrow T的最短路加上一个STS ightarrow T的非树边序列的权值。

    非树边序列:即为你这条路上所走过的非树边,按照从STS ightarrow T的方向。

    因为最短路的长度确定了,那么我们只需这个非树边序列的权值,是所有STS ightarrow T中的合法非树边序列权值中的第KK小即可。

    所以我们来考虑,对于每一个点,STS ightarrow T的非树边序列如果经过它的话,一部分的序列肯定是这个点到TT的非树边序列中的一种,所以对于每一个点,我们用一个小根堆来维护它到TT的非树边序列值。

    那么对于一个点vv,我们如何快速得到它的小根堆(排好序的非树边序列)呢?

    我们可以由当前的点vv到父亲f[v]f[v]的非树边加上f[v]f[v]的小根堆的状态即可,所以为了方便更新和减少空间开销,所以我们要用可持久化堆(这里使用左偏树)。

    然后先预处理所有点的序列,然后我们每次取出最小的一个,然后加入新的状态,直到更新到第KK个,答案就为最短路长度加上第KK个非树边序列的值即可。

    复杂度为O(nlogn+mlogm+klogk)O(nlogn+mlogm+klogk)

    这个复杂度是比较稳定的(缺点是不好记录路径)。

    下面上用可持久化左偏树+Spfa m Spfa实现的代码:

    注意:有两种情况

    • 允许点重复走的:当S=TS=T时,要减去一条SS到自己的路径。
    • 不允许重复走的要记录已经走过的点(一般点数不多,直接boolbool数组或者bitsetbitset记录)
    同样是poj的那道题
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<queue>
    using namespace std;
    const int M=7e5+1,N=1e3+10;
    const int inf=0x7fffffff;
    int n,m;
    
    struct ss{
    	int to,last,len;
    	ss(){}
    	ss(int a,int b,int c):to(a),last(b),len(c){}
    };
    
    struct Graphy{
    	ss g[M];
    	int head[M],cnt;
    	void add(int a,int b,int c){
    		g[++cnt]=ss(b,head[a],c);head[a]=cnt;
    	}
    }G,GI;
    
    struct node{
    	int val,v;
    	node(){}
    	node(int a,int b):val(a),v(b){}
    	bool operator <(const node &a)const{return val>a.val;}
    }pp,qq;
    priority_queue <node> Q;
    
    struct Left_Tree{
    	int h[M],v[M],w[M],son[2][M],tot,root[M];
    	void clear(){tot=0;}
    	#define l(o) son[0][o]
    	#define r(o) son[1][o]
    	int newnode(int vv,int idd,int hh){
    		++tot;
    		w[tot]=vv;v[tot]=idd;h[tot]=hh;
    		l(tot)=r(tot)=0;
    		return tot;
    	}
    	int merge(int a,int b){
    		if(!a||!b) return a|b;
    		if(w[a]>w[b])swap(a,b);
    		int newp=newnode(w[a],v[a],h[a]);
    		l(newp)=l(a);r(newp)=r(a);
    		r(newp)=merge(r(newp),b);
    		if(h[l(newp)]<h[r(newp)])swap(l(newp),r(newp));
    		h[newp]=h[r(newp)]+1;
    		return newp;
    	}
    	void insert(int &o,int vv,int idd){
    		int p=newnode(vv,idd,0);
    		o=merge(p,o);
    	}
    }LT;
    
    int que[M],p,q;bool in[M];
    void spfa(int st,int en,int *dis,Graphy &G){
    	int *head=G.head,v;ss *g=G.g;
    	for(int i=1;i<=n;i++)dis[i]=inf;
    	que[p=q=0]=en;in[en]=1;dis[en]=0;
    	for(;p<=q;p++){
    		int a=que[p%M];
    		for(int i=head[a];i;i=g[i].last){
    			v=g[i].to;
    			if(dis[v]>dis[a]+g[i].len){
    				dis[v]=dis[a]+g[i].len;
    				if(!in[v]){
    					in[v]=1;
    					que[++q%M]=v;
    					if(dis[que[(p+1)%M]]>dis[que[q%M]]){
    						swap(que[(p+1)%M],que[q%M]);
    					}
    				}
    			}
    		}
    		in[a]=0;
    	}
    }
    int fd[M],stk[M],dis[M],top,fs[M];
    void dfs(int a){
    	in[a]=1;stk[++top]=a;int v,w;
    	for(int i=GI.head[a];i;i=GI.g[i].last){
    		v=GI.g[i].to;w=GI.g[i].len;
    		if(!in[v]&&dis[v]==dis[a]+w){
    			fd[v]=a;fs[i]=1;
    			dfs(v);
    		}
    	}
    }
    void Rebuild(){
    	int a,v;
    	for(int w=1;w<=top;w++){
    		a=stk[w];
    		LT.root[a]=LT.root[fd[a]];
    		for(int i=G.head[a];i;i=G.g[i].last){
    			v=G.g[i].to;
    			if(!fs[i]&&dis[v]!=inf){
    				LT.insert(LT.root[a],dis[v]-dis[a]+G.g[i].len,v);
    			}
    		}
    	}
    }
    int Solve_K(int st,int en,int kth){
    	if(!LT.root[st]) return -1;
    	if(kth==1) return dis[st];
    	pp.val=dis[st]+LT.w[LT.root[st]];
    	pp.v=LT.root[st];
    	Q.push(pp);
    	while(!Q.empty()){
    		--kth;
    		qq=Q.top();Q.pop();
    		if(kth==1) return qq.val;
    		int ls=LT.l(qq.v),rs=LT.r(qq.v),o=LT.v[qq.v];
    		int nowv=qq.val;
    		if(LT.root[o]) Q.push(node(nowv+LT.w[LT.root[o]],LT.root[o]));
    		if(ls) Q.push(node(nowv+LT.w[ls]-LT.w[qq.v],ls));
    		if(rs) Q.push(node(nowv+LT.w[rs]-LT.w[qq.v],rs));
    	}
    	return -1;
    }
    
    void add_side(int a,int b,int c){
    	G.add(a,b,c);GI.add(b,a,c);
    }
    
    void calc_K(int ss,int tt,int kk){
    	if(ss==tt)++kk;
    	spfa(ss,tt,dis,GI);
    	if(dis[ss]==inf) {puts("-1");return;}
    	dfs(tt);
    	Rebuild();
    	int ans=Solve_K(ss,tt,kk);
    	printf("%d
    ",ans);
    }
    int s,t,k;
    void work(){
    	int a,b,c;
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;i++){
    		scanf("%d%d%d",&a,&b,&c);
    		add_side(a,b,c);
    	}
    	scanf("%d%d%d",&s,&t,&k);
    	calc_K(s,t,k);
    }
    int main(){work();return 0;}
    

    若有不对或者疑问的地方欢迎提出!!

    博主才学,可能有不清楚或者不对的地方。QAQ

  • 相关阅读:
    FreeRTOS相关转载-(朱工的专栏)
    任务相关的API函数-uxTaskGetSystemState
    STM32用FreeRTOS时任务优先级和中断优先级说明
    STM32标准外设库中USE_STDPERIPH_DRIVER, STM32F10X_MD的含义
    C语言变量和函数命名规范
    Java学习笔记-命令模式
    leetcode-Search in Rotated Sorted Array -java
    Java学习笔记-单件模式
    Java学习笔记-问问题?-->静态方法
    TCP滑动控制
  • 原文地址:https://www.cnblogs.com/VictoryCzt/p/10053378.html
Copyright © 2011-2022 走看看