浅谈K短路算法(KSP)之二(YEN .J算法求解)

对于具有n个顶点和m条边且边的权值非负的简单图(无重边和环),K短路,是指的起点s到终点t的最短路径中第k个最小的。K短路分为有限制的K短路和无限制的K短路,有限制的K短路是指求得的路径中不含有回路(路径上任何一个节点的出现次数不大于1次),无限制的K短路则对求得的路径中没有要求,这篇博客讨论后者。

1、基础理论

1.1 偏离路径

算法主要使用了偏离路径的思想,主要概念如下:

偏离点:两条路径中按顺序出现最后一个相同的点,即出现的第一个不相同的点的前一个点,也可以理解为从偏离点开始,两条路径开始不同

偏离边:两条路径中出现的第一条不相同的边

偏离路径:从偏离点到终点N的路径

设在一个图中有如下两条最短路径p1、p2,起点为节点0,终点为节点5,见上图所示,每条路径经过的顶点如下:

p1:0-2-3-5

p2:0-2-4-5

则p2相对于p1的偏离点是节点2,p2相对于p1的偏离边是2-4,p2相对于p1的偏离路径为2-4-5。

1.2 相关定义

在Yen的算法中,首先定义如下:

A^{k}=1-(2^{k})-(3^{k})-...-Q_{k}^{k}-(N),k=1,2,3,...K,是从起点到终点N的第K短路,其中2^{k}3^{k}、...Q^{k}代表第K短路经上第2个、第3个、第k个节点

A^{k}:第k条最短路径

A^{k-1}:第k-1条最短路径

A^{j},j=1,2,...k-1是个集合,代表前k-1个最短路径的集合

A^{k}是通过A^{j},j=1,2,...k-1求得的,求得的方式如下:

i=1,2,3,..Q_{k-1}i=1,2,3,...Q_{k-1}代表A^{k-1}路径上按照顺序的每个节点,在A^{k-1}路径上,从节点1到节点i的路径设为R_{i}^{k},在节点i上偏离至一个新节点,设此新节点为i+1,要求此i+1节点不与集合A^{j},j=1,2,...k-1在节点i上的下一个节点相同,同时计算通过新节点i+1到达终点N的最短偏离路径,记为S_{i}^{k}S_{i}^{k}路径中的节点不能R_{i}^{k}中的节点相同,则A_{i}^{k}=R_{i}^{k}+S_{i}^{k},代表的是在偏离节点i上,相对于A^{k-1}的偏离路径。由上述步骤可知,A_{i}^{k}是无环的。

2 求解步骤

下面给出具体的求解A^{k}的步骤。

首先维护两个链表ListA,ListB,ListA是已求得A^{k}的集合,ListB是A^{k}的候选集合

(1)求A^{1}A^{1}的求得方式很多,dijkstra,spfa等。在Yen的论文中,求A^{1}是用Yen自己的方法,使用的图是不带负环的图。

(2)对于i=1,2,3,...Q_{k-1}代表A^{k-1}路径上按照顺序的每个节点,已经求得A^{j},j=1,2,...k-1,如果i节点在集合A^{j}中出现过,设d_{iq}=∞,其中q是A^{j}集合中的i+1节点。(i节点至少在A^{k-1}路径上出现过),结束本次循环后,d_{iq}恢复原值。

(3)对图求得节点i到终点N的最短偏离路径,即S_{i}^{k},且这条路径上(S_{i}^{k})的节点与前半部分路径(A^{k-1}上,节点1到节点i的路径,即R_{i}^{k})的节点不能相同,如果有多个最短偏离路径,则任选其一

(4)A_{i}^{k}=R_{i}^{k}+S_{i}^{k},将求得的A_{i}^{k』加入候选集合ListB。将ListB中的取最小值加入LIstA。如果有多个最小值,取节点数少的那个

(5)如果ListA中已经有K个,则算法结束,最后一个加入的即为第K短路,否则进入k+1次循环。

步骤说明:

(1)设d_{iq}=∞的目的是在偏离点i上强制偏离至一个新的节点,新的节点与A^{j},j=1,2,...k-1i+1节点均不相同。这样形成的新路径总值不小于A^{k-1},即A_{i}^{k}>=A^{k-1}

(2)对A^{k-1}路径节点进行偏离,实质上是对除了终点之外的所有点进行偏离,即i=1,2,3,...Q_{k-1},终点N不进行偏离

3求解过程

使用的无向图如下图所示,求A^{5}

如上图所示,图中有6个顶点,9条边;即n=6,m=9。

先求得A^{1},A^{1}=A-D-E-F,值12,这样ListA、ListB的状态如下:

ListAListB
A^{1}=A-D-E-F 
  
  

 

 

 

 

 

(1)对A^{1}=A-D-E-F进行扩展,求得A^{2}

偏离点A^{k-1}z中设d_{iq}=∞R_{i}^{k}A^{k-1}外,A^{j}中设置d_{iq}=∞偏离路径生成的候选路径操作
AA-D=∞NULLNULLA-B-C-F 16A-B-C-F 16加入ListB
DD-E=∞A-DNULLD-B-C-F 14A-D-B-C-F 16加入ListB
EE-F=∞A-D-ENULLE-C-F 9A-D-E-C-F 12加入ListB

 

 

 

 

 第三条候选路径最短,将第三条路径A-D-E-C-F加入ListA,距离12,则此时A^{2}=A-D-E-C-F,ListA、ListB的状态如下:

ListAListB
A^{1}=A-D-E-FA-B-C-F 16
A^{2}=A-D-E-C-FA-D-B-C-F 16
  
  

 

 

 

 

 

 

(2)对A^{2}=A-D-E-C-F进行扩展,求得A^{3}

偏离点A^{k-1}z中设d_{iq}=∞R_{i}^{k}A^{k-1}外,A^{j}中设置d_{iq}=∞偏离路径生成的候选路径操作
AA-D=∞NULLNULLA-B-C-F 16A-B-C-F 16ListB中已有此路径,不再加入
DD-E=∞A-DNULLD-B-C-F 14A-D-B-C-F 16ListB中已有此路径,不再加入
EE-C=∞A-D-EE-F=∞E-B-C--F 12A-D-E-B-C-F 15加入ListB
CC-F=∞A-D-E-CNULL不存在不存在 

 

 

 

 

 

第一条、第二条候选路径已存在,将第三条候选路径加入ListB,并ListB中最短的路径A-D-E-B-C-F 加入ListA,此时

ListA、ListB的状态如下:

ListAListB
A^{1}=A-D-E-FA-B-C-F 16
A^{2}=A-D-E-C-FA-D-B-C-F 16
A^{3}=A-D-E-B-C-F 
  

 

 

 

 

 

 

(3)对A^{3}=A-D-E-B-C-F进行扩展,求得A^{4}

偏离点A^{k-1}z中设d_{iq}=∞R_{i}^{k}A^{k-1}外,A^{j}中设置d_{iq}=∞偏离路径生成的候选路径操作
AA-D=∞NULLNULLA-B-C-F 16A-B-C-F 16ListB中已有此路径,不再加入
DD-E=∞A-DNULLD-B-C-F 14A-D-B-C-F 16ListB中已有此路径,不再加入
EE-B=∞A-D-EE-C=∞,E-F=∞不存在不存在 
BB-C=∞A-D-E-BNULL不存在不存在 
CC-F=∞A-D-E-B-CNULL不存在不存在 

 

 

 

 

 

 

此次循环,没有新的序列加入,将原ListB中距离最短的A-B-C-F的加入ListA中,此时ListA、ListB的状态如下:

ListAListB
A^{1}=A-D-E-FA-D-B-C-F 16
A^{2}=A-D-E-C-F 
A^{3}=A-D-E-B-C-F 
A^{4}=A-B-C-F 

 

 

 

 

 

 

(4)对A^{4}=A-B-C-F进行扩展,求得A^{5}

偏离点A^{k-1}z中设d_{iq}=∞R_{i}^{k}A^{k-1}外,A^{j}中设置d_{iq}=∞偏离路径生成的候选路径操作
AA-B=∞NULLA-D=∞不存在不存在 
BB-C=∞A-BNULLB-E-F 13A-B-E-F 21加入ListB
CC-F=∞A-B-CNULLC-E-F 12A-B-C-E-F 22加入ListB

 

 

 

 

将ListB中最短的路径:A-D-B-C-F 加入ListA,此时

此时ListA、ListB的状态如下:

ListAListB
A^{1}=A-D-E-FA-B-E-F 21
A^{2}=A-D-E-C-FA-B-C-E-F 22
A^{3}=A-D-E-B-C-F 
A^{4}=A-B-C-F 
A^{5}=A-D-B-C-F 

 

 

 

 

 

 

 

已求得A^{5},算法结束。

4算法实现

用C++编写,编译器g++,代码无出错处理流程,仅供参考

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <bits/stdc++.h>
#include <algorithm>
#include <typeinfo>
using namespace std;
#define MAXNODE 64
#define MAXEDGE 100
#define INFINITE 1e8
class EdgeNode{ //图用邻接表来存储,这是边表节点
	public:
		EdgeNode(char to='Z',int weight=1):to(to),weight(weight),weight2(weight){next=NULL;}
		EdgeNode(const EdgeNode &en){
			this->to=en.to;
			this->weight=en.weight;
			this->weight2=en.weight;
			this->next=en.next;
			
		}
		EdgeNode & operator=(const EdgeNode &en)
		{
			this->to=en.to;
                        this->weight=en.weight;
			this->weight2=en.weight;
                        this->next=en.next;
                        
		}
		~EdgeNode(){}
		char to;
		int weight; //原值,不改变
		int weight2; //用于修改数值(即设置无穷大)并重置
		EdgeNode *next;
};
class VertexNode{   //顶点表节点
	public:
	VertexNode(char name='Z',EdgeNode *firstEdge=NULL):name(name),firstEdge(firstEdge) {}
	char name;
	EdgeNode *firstEdge;
	~VertexNode(){}

};
VertexNode vn[MAXNODE];//全局数据结构,
int createGraph(int n,int m,int flag){ //构造图
	char from,to;
	int weight;
for(int i=0;i<m;i++)
{
cin>>from>>to>>weight;
if(flag==0){

	if(vn[from-65].name=='Z') vn[from-65].name=from;
	if(vn[to-65].name=='Z') vn[to-65].name=to;

	EdgeNode *edge_new=new EdgeNode(to,weight);
	EdgeNode *p=vn[from-65].firstEdge;
	if(vn[from-65].firstEdge==NULL){
		vn[from-65].firstEdge=edge_new;
	}
	else 
	{
		while(p->next!=NULL)
			p=p->next;
		 p->next=edge_new;

	}
	EdgeNode *edge_new2=new EdgeNode(from,weight);
	p=vn[to-65].firstEdge;
	if(vn[to-65].firstEdge==NULL)
                vn[to-65].firstEdge=edge_new2;
        else
        {
                while(p->next!=NULL)
                        p=p->next;
		 p->next=edge_new2;

        }
}
else {
	 if(vn[from-65].name=='Z') vn[from-65].name=from;
        if(vn[to-65].name=='Z') vn[to-65].name=to;//这个点没有出边,这个点要保存在数组里
        EdgeNode *edge_new=new EdgeNode(to,weight);
        EdgeNode *p=vn[from-65].firstEdge;
        if(vn[from-65].firstEdge==NULL)
                vn[from-65].firstEdge=edge_new;
        else
        {
                while(p->next!=NULL)
                        p=p->next;
		p->next=edge_new;
        }
     }
}
return 1;
}
pair<int,vector<char>> dijkstra(char from,char to,int *t) //求最短路径
{
long max=1e8;  //无穷大的值
int distance=0;
char parent[MAXNODE];//更新时记录每个节点的父节点,便于找到最短路径,只有这个距离更新,此节点的父节点才会更新
for(int i=0;i<MAXNODE;i++)
	parent[i]='Z';//初始化所有节点父节点为'Z'
vector<char>vec;//存储的是T中的每个节点,即最短路径,
vector<pair<int,char>>queue;//代表的是T,即未扩展边的集合,用vector 实现
for(int i=0;i<MAXNODE;i++)
{
	if(vn[i].name!='Z'){
		if(vn[i].name==from&&t[vn[i].name-65]==0)//排除点不再入队
                queue.push_back(make_pair(0,vn[i].name));//起点的距离值为0
		else if(t[vn[i].name-65]==0)
		queue.push_back(make_pair(max,vn[i].name));//初始化其他点的距离为max
	}

}

vector<pair<int,char>>::iterator it=queue.begin();
while(!queue.empty())
{ 
	sort(queue.begin(),queue.end());//按距离值从小到大排序
	vector<pair<int,char>>::iterator tmp=queue.begin();
	if(queue.begin()->second==to) {
		distance=queue.begin()->first;//当弹出节点是终点时,便是终点的最短距离
		if(distance >=1e8)//终点的路径值大于等于无穷大,代表此路径不存在
	{
		distance =-1; //显示找不到路径,设置距离值为负值
		break;
	}
	}
	EdgeNode *p=vn[tmp->second-65].firstEdge;
	int d=tmp->first;//存储此节点出队的距离值
	while(p!=NULL)
	{
		
		for(it=queue.begin()+1;it!=queue.end();it++)
		{
			if(p->to==it->second)//在T中找到要更新的点
			{
				if(d+p->weight2<it->first)
				{ //需要更新,相等的话则不再更新
					it->first=d+p->weight2;
					parent[it->second-65]=tmp->second; //更新父节点
				}
				 
				break;
			}
		}
		p=p->next;
	}
	queue.erase(queue.begin());//删除首节点
}

      char c=to;
	int i=1;
        while(c!=from&&c!='Z')
        {
		 if(i>MAXNODE)//大于最大节点数,退出循环
			break;
       	 vec.push_back(c);
       	 c=parent[c-65];
		 i++;
        }
	if(c==from)
	vec.push_back(c);//vector 是按照逆序存储的每个节点
	return make_pair(distance,vec);

}
void reset()
{//修改后,将图复位
for(int i=0;i<MAXNODE;i++){
EdgeNode *p=vn[i].firstEdge;
while(p!=NULL)
{
p->weight2=p->weight;
p=p->next;
}
}
}
inline bool setinfinite(char start,char end) //设置无穷大
{
	int result=0;
EdgeNode *p=vn[start-65].firstEdge;//在vn上修改
while(p!=NULL)
{
if(p->to==end)
{
p->weight2=INFINITE;//操作的是weight2
result=1;
}
p=p->next;
}
p=vn[end-65].firstEdge;//无向图,也适用于有向图
while(p!=NULL)
{
if(p->to==start)
{
	p->weight2=INFINITE;
	result=1;
}
p=p->next;
}
return result;
}
bool search(char node,vector<pair<int,vector<char>>>ListA) //在ListA中将节点node开始的所有边设为infinite
{
for(vector<pair<int,vector<char>>>::iterator it=ListA.begin();it!=ListA.end();it++)
{
	for(vector<char>::iterator it2=it->second.end()-1;it2>it->second.begin();it2--) //按照顺序逐个设置无穷大
	{
		if(*it2==node)
		{
			vector<char>::iterator it3=it2-1;
			if(!setinfinite(*it2,*it3))
				return 0;


		}
	}
}
return 1;
}
bool exist(vector<pair<int,vector<char>>>List,vector<char>vec)//新序列是否在ListB中出现过
{
for(vector<pair<int,vector<char>>>::iterator it=List.begin();it!=List.end();it++)
{
	if(it->second==vec)
		return 1;
}
return 0;
}
void print(vector<pair<int,vector<char>>>List) //打印ListA、ListB的状态
{
if(!List.empty())
for(vector<pair<int,vector<char>>>::iterator it=List.begin();it!=List.end();it++)
{
	for(vector<char>::iterator it2=it->second.end()-1;it2>=it->second.begin();it2--)
		cout<<*it2<<"  ";
	cout<<it->first<<endl;
}

}
int main(void)
{
	vector<pair<int,vector<char>>>ListA,ListB;//第一个代表此路径值,第二个代表路径节点序列
	int excluded[MAXNODE]; //进行dijkstra时,排除的R_{i}^{k}的节点
	memset(excluded,0,MAXNODE*sizeof(int));
	int n,m;
	int flag=0; //0代表无向图,1代表有向图
	cin>>n>>m>>flag;
	createGraph(n,m,flag);
	char from,to;
	int k;
	cin>>from>>to>>k;
	pair<int,vector<char>>A1;
	A1=dijkstra(from,to,excluded);
	ListA.push_back(A1);//将A1加入ListA

for(int i=1;i<=k;i++)
{
	reset();//将图复位
	vector<pair<int,vector<char>>>::iterator end=ListA.end()-1;//最底元素是需要进行扩展的路径
	vector<char>temp=(ListA.end()-1)->second;
	memset(excluded,0,MAXNODE*sizeof(int));
	vector<char>result;
	int total=0;//路径前半部分的值
	for(vector<char>::iterator iter=(end->second.end())-1;iter!=end->second.begin();iter--) //对偏离节点进行扩展
	{//不包括vector路径的起始节点,即不包括终点
	search(*iter,ListA);//对偏离点进行扩展
	if(iter<(end->second.end()-1)) //不是第一个偏离点
	{
		vector<char>::iterator iter2=iter+1;
		int weight=0;
		EdgeNode *p=vn[*iter2-65].firstEdge;//使用原值数组vn
		while(p!=NULL)
		{
			if(p->to==*iter){
				weight=p->weight;
				break;
			}
			p=p->next;
		}
			total=total+weight;//路径前半部分的值,随着偏离点的前进。路径值也会逐渐增大
			vector<char>::iterator iter3=iter+1;
			if(iter3<end->second.end())
			{
			excluded[*iter3-65]=1; //进入dijkstra,加入排除节点
			result.push_back(*iter3); //排除节点即路径前半部分的节点
			}
	}
	pair<int,vector<char>>tmp;
	tmp=dijkstra(*iter,to,excluded);
	if(tmp.first>0){ //求得的路径是有效值
	tmp.first=total+tmp.first;//前半部分距离+后半部分距离=总距离
	if(!result.empty()){//将路径前半部分加入,形成一个完整的路径
	for(vector<char>::iterator it=result.end()-1;it>=result.begin();it--){
		tmp.second.push_back(*it);
		}
	}
	if(!exist(ListB,tmp.second)) //如果ListB不存在,才加入ListB
		{
		ListB.push_back(tmp);
		}
	}
	sort(ListB.begin(),ListB.end());//对ListB排序
	} //偏离点的循环
	if(!ListB.empty())
	{ //将ListB中的首元素移入ListA
	vector<pair<int,vector<char>>>::iterator begin=ListB.begin();
	ListA.push_back(*begin);
	ListB.erase(begin);
	}
	else 
	{     
	if(i!=1) //队列B为空,且不是循环刚开始的时候,代表可能的K短路已全部找到,余下已无法再找。退出循环
	cout<<"K max :"<<i<<endl;
	break;
	}
	
} // i的for循环
print(ListA);//打印结果
exit(0);
} //main 主函数

输入上述示例图:

6 9 0 //顶点数、边数和是否有向图
A B 8
B D 6    
A D 2
D E 1
B E 4
B C 2
C E 3
C F 6
E F 9
A F 4 //起点、终点和K值

输出结果如下:
A  D  E  F  12
A  D  E  C  F  12
A  D  E  B  C  F  15
A  B  C  F  16


5 算法时间、空间复杂度分析

在第K个循环中,设偏离点的个数为q

(1)求最短路径使用Yen自己的算法,则d_{ij}>=0的情况下,需要\frac{1}{2}(N-1)^{2}+\frac{1}{2}(N-2)^{2}+...\doteq \frac{1}{6}qN^{3}次加法和(N-1)^{2}+(N-2)^{2}+...\doteq \frac{1}{3}qN^{3}次比较,其中q为偏离点的个数,如果如果允许d_{ij}<0,则需要\frac{1}{4}(N-1)^{2}+\frac{1}{4}(N-2)^{2}+...\doteq \frac{1}{16}qN^{3}次加法和\frac{1}{4}(N-1)^{2}+\frac{1}{4}(N-2)^{2}+...\doteq \frac{1}{16}qN^{3}次比较,其中N为图中顶点属

(2)需要的空间复杂度大约为N^{2}+KN

 

参考资料:

(1)Finding the K Shortest Loopless Paths in a Network Jin.Y.Yen. https://www.docin.com/p-1819184782.html

(2)Yen的K条最短路径算法(KSP).https://www.jianshu.com/p/ea0e6894259b

 

 

 

 

 

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值