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

本篇博客中使用的无向图实例如下图所示

如上图所示,求解节点A至节点C的K短路。本篇博客主要使用经典的A*算法。

1 A*算法简介。

通用图搜索路径常用的启发式搜索

其中f(x)称为评估函数,g(n)为从初始节点S到节点n的实际代价,h(n)是从节点n到目标节点T的最 优路径的评估代价,被称为启发式函数,它体现了问题的启发式信息。

由此引出A*算法:

定义评估函数:

在上式中,g*(n)是从起始节点S到节点n的最短路径的代价,h*(n)是从节点n到目标节点T的最短路径的代价。shi因为有可能最短路径还没找到,所以一般情况下有g(n)>=g*(n)

同时h(n)<=h*(n),就是说h(n)是h*(n)的下界,这个限制确保A*算法可以找到最优解。对于一个问题,启发式函数h(n)的设计通常有多种,一般有如下关系

(1)h(n)=0,A*算法退化为dijkstra算法,可以找到最优解

(2)h(n)<=h*(n),A*算法可以找到最优解,不过收敛速度较慢,h(n)越小,收敛速度越慢

(3)h(n)=h*(n),A*算法可以找到最优解,收敛速度很快,扩展的节点全是最短路径上的点

(4)h(n)>h*(n),A*算法不一定能找到最优解,但收敛速度很快。

2 求解过程

用A*算法求解K短路问题,可以概括为如下步骤。首先,定义评估函数

,其中g(n)是从起始节点S到达节点的n的实际代价,定义为从起始节点S到达节点n的路径上所经过边的权值之和,h(n)为从节点n到达目标节点的最短路径的代价,记为dis[],实现的方法是构建反图,以终点T为起点对全图进行一次dijkstra算法得到(在这里,h(n)=h*(n)),以上图为例,从节点C到各个节点的dis值如下:

dis[C]=0,dis[B]=2,dis[A]=3,dis[D]=4

定义如下结构:queue[]为优先级队列,count[]为节点出现的次数。这样f(n)代表从起点节点到目标节点的最短路径的费用。刚开始,queue[]只有起始节点,算法步骤如下

(1)从优先级队列选取队头元素,如果fx值相同则选取gx较小的

(2)用队头元素去更新其子女节点的f(n)值,并将子女节点入队

(3)当取出的队头元素是终点时,计算其出现次数,若是K次,算法结束,否则将其子女入队。

对上图,求解节点A到节点C的K短路,具体的求解过程如下:

(b)queue[]={A}

g(1)=0,h(1)=dis[A]=3;

f(1)=g(1)+h(1)=0+3=3;

(b)节点A出队,其子女节点B、C、D入队,

用f(1)更新其子女节点B、C、D的f值

g(2)=g(1)+w(A-B)=0+1=1(w(A-B)代表边A-B的权值)

h(2)=dis[B]=2;

f(2)=g(2)+h(2)=1+2=3;对应节点为B

g(3)=g(1)+w(A-C)=0+4=4;

h(3)=dis[C]=0;

f(3)=g(3)+h(3)=4+0=4;对应节点为C;

g(4)=g(1)+w(A-D)=0+1=1;

h(4)=dis[D]=4;

f(4)=g(4)+h(4)=1+4=5;对应节点为D

queue[]={2,3,4}

(c)节点2出队,B的子女节点A、C入队

g(5)=g(2)+w(B-A)=1+1=2;

h(5)=dis[A]=3;

f(5)=g(5)+h(5)=5; 对应节点为A

g(6)=g(2)+w(B-C)=1+2=3;

h(6)=dis[C]=0;

f(6)=g(6)+h(6)=3; 对应节点为C

queue[]={6,3,5,4},

(d)节点6出队,节点C的子女A、B、D 入队

节点6对应的节点是C,即终点C,也是C的第一次出队,即第一短路,count[C]++

g(7)=g(6)+w(C-A)=3+4=7;

h(7)=dis[A]=3;

f(7)=g(7)+h(7)=10;

g(8)=g(6) +w(C-B)=3+2=5;

h(8)=dis[B]=2;

f(8)=g(8)+h(8)=5+2=7,对应节点为B

g(9)=g(6)+w(C-D)=3+6=9;

h(9)=dis[D]=4;

f(9)=g(9)+h(9)=9+4=13;

queue[]={3,5,4,8,7,9}

(e)节点3出队,节点3(节点C)的子女A、B、D入队

节点3对应的图中节点即是节点C,是终点,这是终点的第2次出队,代表第2短路,count[C]++

以此类推,求出最短路

当节点较多时,优先级队列的元素较多,所以优先级队列的容量尽量大些.

3 实现

用C++编写,g++编译,实现无出错处理,仅供参考

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <bits/stdc++.h>
using namespace std;
#define MAXNODE 64  //最大顶点数,顶点是字母,存储的是从大写字母A开始的64个字符
#define MAXEDGE 100 //最大边数
class EdgeNode{ //图用邻接表存储,这是邻接表中的边表节点,类中成员均为pulic,便于访问和修改public:EdgeNode(char to='Z',int weight=1):to(to),weight(weight){next=NULL;}EdgeNode(const EdgeNode &en){this->to=en.to;this->weight=en.weight;this->next=en.next;}EdgeNode & operator=(const EdgeNode &en){this->to=en.to;this->weight=en.weight;this->next=en.next;}~EdgeNode(){}char to;int weight;EdgeNode *next;
};
class VertexNode{ //邻接表中的顶点表节点,所有成员均为publicpublic:VertexNode(char name='Z',EdgeNode *firstEdge=NULL):name(name),firstEdge(firstEdge) {}char name;EdgeNode *firstEdge;~VertexNode(){}};
VertexNode vn[MAXNODE],vnr[MAXNODE];//正图、反图,用正向和反向的邻接表表示
int dis[MAXNODE];//每个节点的dis值
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;//添加正图中的边表节点}if(vnr[to-65].name=='Z') vnr[to-65].name=to;
if(vnr[from-65].name=='Z') vnr[from-65].name=from; //这个点没有出边,这个点要保>存在数组里EdgeNode *edge_new2=new EdgeNode(from,weight);
p=vnr[to-65].firstEdge;if(vnr[to-65].firstEdge==NULL)vnr[to-65].firstEdge=edge_new2;else{while(p->next!=NULL)p=p->next;p->next=edge_new2;//添加反图中的边表节点}}
}
if(flag==0)
for(int i=0;i<MAXNODE;i++) //无向图的复制vnr[i]=vn[i];return 1;
}bool dijkstra(char from,char to) //dijkstra 求解dis值
{
//V为顶点集,S为已求得最短距离的点的集合,T为余下点的集合,初始时,S={},T=V,T用带pair的vector实现,当T为空时,代表所有点均以到达集合S中,算法结束,当一个节点从集合T中迁移至S中时,这个顶点对应的距离值便是此节点到起点的最短距离值。long max=1e8;
char parent[MAXNODE];//更新时记录每个节点的父节点,只有这个距离更新,此节点的父>节点才会更新
for(int i=0;i<MAXNODE;i++)parent[i]='Z';
vector<pair<int,char>>queue;//不是优先级队列,第一个代表距离,第二个代表节点
for(int i=0;i<MAXNODE;i++)
{if(vnr[i].name!='Z'){if(vnr[i].name==to)queue.push_back(make_pair(0,vnr[i].name));//起点的距离值为0elsequeue.push_back(make_pair(max,vnr[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(dis[queue.begin()->second-65]>queue.begin()->first)dis[queue.begin()->second-65]=queue.begin()->first;//队首的距离值为最短的,而且要出队EdgeNode *p=vnr[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->weight<it->first){ //需要更新,相等的话则不再更新,相等代表距离一样,但经过的顶点更多it->first=d+p->weight;parent[it->second-65]=tmp->second; //更>新父节点}break;}}p=p->next;}queue.erase(queue.begin());//删除起始节点
}
Edg
}
bool Astar(char from,char to,int k)//A star 求解K短路
{if(vn[from-65].name=='Z'){cout<<"node doesn't exist"<<endl;return 0;}
int count=0;//终点出现次数
int i=1,loc=1;    //步数
int fx[100],gx[100],hx[100];//A star中的函数值,即扩展的节点序列,暂定100
char node[100];             //存储节个序列中实际对应的每个节点
int parent[100];           //每个序列的父节点
for(int i=0;i<100;i++)
{fx[i]=0;gx[i]=0;hx[i]=0;node[i]='Z';parent[i]=0;
}
//添加父节点
priority_queue<pair<pair<int,int>,int>,vector<pair<pair<int,int>,int>>,greater<pair<pair<int,int>,int>>>q; //优先级队列,第一个int存储的是fx值,第二个存储的是gx值,第三个存储的是扩展的节点序列,满足当fx值相等时,则选择fx较小的
gx[1]=0;hx[1]=dis[from-65];node[1]=from;//先将起点入队
fx[1]=gx[1]+hx[1];
parent[0]=-1;  //便于沿着父节点数组一直找到起点
parent[1]=0;
q.push(make_pair(make_pair(fx[1],gx[1]),1));
while(!q.empty())
{pair<pair<int,int>,int>tmp=q.top();q.pop();if(node[tmp.second]==to){//找到终点vector<char>vec;//用vector存储此条路径int now=tmp.second;while(parent[now]!=-1){vec.push_back(node[now]);//节点存入vectornow=parent[now];//找到父节点}EdgeNode *p=vn[node[tmp.second]-65].firstEdge;while(p!=NULL) //将子女节点入队{++loc;gx[loc]=gx[tmp.second]+p->weight;node[loc]=p->to;parent[loc]=tmp.second;hx[loc]=dis[p->to-65];fx[loc]=gx[loc]+hx[loc];q.push(make_pair(make_pair(fx[loc],gx[loc]),loc));p=p->next;}count++;if(count==k){vector<char>::iterator it=vec.end()-1;cout<<"k="<<k<<endl;for(it;it>=vec.begin();it--)cout<<*it<<"  ";//输出节点序列cout<<fx[tmp.second]<<endl;//输出总的fx值break;//找到第K短路,退出}}else {//将当前节点的子女节点入队EdgeNode *p=vn[node[tmp.second]-65].firstEdge;while(p!=NULL){++loc;gx[loc]=gx[tmp.second]+p->weight;node[loc]=p->to;parent[loc]=tmp.second;hx[loc]=dis[p->to-65];fx[loc]=gx[loc]+hx[loc];q.push(make_pair(make_pair(fx[loc],gx[loc]),loc));p=p->next;}}}
return 1;
}int main(void)
{memset(dis,10000,sizeof(dis));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;//输入起点、终点和Kdijkstra(from,to);Astar(from,to,k);exit(0);
}

以上图为例,输入:

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

输出如下:

k=3
  A  B  A  B  C  5

从输出看出,第3条短路确实有环,从A-B-A,所以这个算法是解决的无限制的K短路,在实际应用中,有环的K短路一般情况下没有意义。导致出现环路的原因是某一个路径一个节点的出现次数大于1次,如果我们在当前节点入队之前,将当前节点实际对应的节点与所有的父节点相比较,如果父节点中已经出现过此节点,则表明当前节点对应节点在路径的出现次数大于1次,当前节点不再入队,则得到的路径便是没有环路。因此在每个结点入队前,判断此节点路径上的所有父节点是否与当前节点的对应节点相同,是,则不再入队。这样一来,优先级队列容量相对于前者要小。将上面代码q.push(make_pair(make_pair(fx[loc],gx[loc]),loc));的内容修改为:

                int result=1; //先设定没有相同的int now=tmp.second;while(parent[now]!=-1){if(node[now]==p->to) //父节点中找到当前节点相同的{result=0;break;}now=parent[now];}if(result)q.push(make_pair(make_pair(fx[loc],gx[loc]),loc));

因为在无环的K短路中,K值是有限制的,即一个图,无环短路的路径数量有个上线值,在Astar函数返回前添加如下代码:

if(count<k)
        cout<<"K max:"<<count<<endl;

代表大于k max值后的路径均找不到。

参考资料:

(1)https://zhuanlan.zhihu.com/p/34665151

(2)https://www.cnblogs.com/shuaihui520/p/9623597.html

浅谈K短路算法(KSP)之一(A*算法求解)相关推荐

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

    对于具有n个顶点和m条边且边的权值非负的简单图(无重边和环),K短路,是指的起点s到终点t的最短路径中第k个最小的.K短路分为有限制的K短路和无限制的K短路,有限制的K短路是指求得的路径中不含有回路( ...

  2. 浅谈MySQL索引背后的数据结构及算法

    2019独角兽企业重金招聘Python工程师标准>>> 摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存 ...

  3. 浅谈神经网络之链式法则与反向传播算法

    反向传播是训练神经网络最重要的算法,可以这么说,没有反向传播算法就没有深度学习的今天.但是反向传播算法涉及一大堆数据公式概念.所以我们了解导数计算过程以及要介绍的新的复合函数多层求导计算过程. 链式法 ...

  4. 浅谈决策树算法以及matlab实现ID3算法

    决策树方法在分类.预测.规则提取等领域有着广泛的应用.在20世纪70年代后期和80年代初期,机器学习研究者J.Ross Quinilan提出了ID3算法以后,决策树在机器学习.数据挖掘领域得到极大的发 ...

  5. 浅谈水下声速剖面及声线追踪算法

    声波传播速度是海洋中最重要的声学参数,它是影响声波在海洋中传播的最基本的物理量参数.有关测量研究表明,水中声速是温度.盐度和静压力的函数,声速随着温度.盐度和压力的增加而增加.海洋的不均匀性和多变性强 ...

  6. POJ--2449--Remmarguts#39; Date【dijkstra_heap+A*】第K短路

    链接:http://poj.org/problem?id=2449 题意:告诉你有n个顶点,m条边,并把这些边的信息告诉你:起点.终点.权值.再告诉你s.t.k.需求出s到t的第k短路,没有则输出-1 ...

  7. 乘法逆元 java_浅谈乘法逆元(示例代码)

    浅谈乘法逆元 乘法逆元,一般用于求解(frac{A}{C}(mod ~ P))的值,因为我们通过模的定义可以知道上式显然不等于(frac{A \% P}{B \% P}).例子有很多不再举了.那么如果 ...

  8. 浅谈最短路径O(n^3)万(蒟)能(蒻)算法——————Floyd《最短路径·O(n^3)Floyd篇》

    浅谈 最短路径O(n^3)万(蒟)能(蒻)算法------Floyd <最短路径·O(n^3)Floyd篇> 暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图. ...

  9. 浅谈算法和数据结构: 五 优先级队列与堆排序

    原文:浅谈算法和数据结构: 五 优先级队列与堆排序 在很多应用中,我们通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理次高的对象.最简单的一个例子就是,在手机上玩游戏 ...

最新文章

  1. linux 大量的TIME_WAIT解决办法
  2. python快速写入hbase_Python生成HBase 10w+ 条数据说明
  3. Python进阶之路:namedtuple
  4. golang获取变量地址值和指针变量示例
  5. amoeba for mysql配置_Amoeba for mysql 读写分离
  6. 基于pip的安装lxml库报错解决方案
  7. hdu 1754 I Hate It(分块做法)
  8. CIKONSS-纯CSS实现的响应式Icon
  9. [单选题]对下面的表达式描述正确的是: (T/t)(M/m)
  10. java反射创建实例_Java反射创建实例
  11. DevExpress ChartControl 折线图简单使用
  12. 安装可视化linux界面
  13. 解决:视频中有噪声(电流声)怎么办的问题(简单实用高效的视频降噪方法)
  14. PHP月考---给自己建个错题本
  15. C/C++外卖点餐管理程序
  16. 建筑师们终于在虚拟空间开双年展了! | 绿洲 · 虚拟现实专栏
  17. 以太坊宠物商店 - 记录第一个Dapp
  18. 循环渐进NsDoor(一)
  19. 用Pandas分析了75w多条数据,揭秘美国选民的总统喜好
  20. Babylonjs 执行 blender 动画

热门文章

  1. CSS3边框图片、边框阴影、文本阴影
  2. day29 java 的IO流(2)
  3. 无连接可靠传输_FPC连接器的特点以及弹片微针模组的作用
  4. maple 2018 窗口关闭提示乱码_iPhone最废柴却无法关闭的俩功能,我终于屏蔽了!...
  5. 大数据— Hadoop
  6. android x86 sleep,如何打开Android X86对houdini的支持
  7. java8如何兼容java7_尽管使用Java 8功能,项目如何支持Java 7
  8. snm算法_网络发现中SNM及ICM算法的探讨
  9. pictureselector 压缩_Android 多图选择器PictureSelector 使用
  10. html文件脚本,我想要从html文件或js脚本执行jsx脚本