前段时间要做一个Project,在建模过程中发现要求出一个网络拓扑中的前K条最短路才能进行后续的运算,自己研究了一段时间,实现了java版本的YEN--ksp算法。
Yen's算法是Yen 在1971 年提出的以其名字命名 的Yen 算法。Yen's算法基于偏离路径算法思想,算法原理详见https://en.wikipedia.org/wiki/Yen%27s_algorithm
我自己实现的这个算法比较粗糙,还有不少可以优化的地方,比如第一次Dijkstra(s)的时候可以把路径信息存储起来,以便后续无需计算直接使用,这里我就不实现了。
算法所需的数据采用随机生成的方式,并且将设置YEN_ksp(0,55,50),即求出Node-0到Node-55的前50条最短路
package lib.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;
public class Yen_Ksp {
public static int n=100;//节点数
public static double[][] edges=new double[n][n];
public static double[][] edges_tmp=new double[n][n];
public static ArrayList<Path> ans_path=new ArrayList<>();//K最短路径
public static Map<Path, Boolean> dev_path_sql=new HashMap<Path, Boolean>();//用于查询偏离路径是否重复
public static Queue<Path> dev_path_queue = new PriorityQueue<>();//用于存放偏离路径
public static boolean[] s=new boolean[n];//待扩展的节点集合,false:未扩展
public static int[] prev_node=new int[n];//存放前向节点
public static double[] dist=new double[n];//路径的权重和
public static final double INF=10000000;
public static void Dijkstra(int v0){//v0-> otherNode
for(int i=0;i<n;++i){//初始化
dist[i]=edges_tmp[v0][i];
s[i]=false;
if(i!=v0&&dist[i]<INF) prev_node[i]=v0;
else prev_node[i]=-1;
}
s[v0]=true;
dist[v0]=0;
for(int i=0;i<n-1;++i){//从顶点v0确定n-1条最短路径(n-1个顶点)
double min=INF;
int u=v0;
for(int j=0;j<n;++j){//选择当前集合T中具有最短路径的顶点 u
if(!s[j]&&dist[j]<min){
u=j;
min=dist[j];
}
}
s[u]=true;//将顶点u加入到集合s,表示它的最短路径已求得
for(int k=0;k<n;++k){
if(!s[k]&&edges_tmp[u][k]<INF&&dist[u]+edges_tmp[u][k]<dist[k]){
dist[k]=dist[u]+edges_tmp[u][k];
prev_node[k]=u;
}
}
}
}
//路径信息存储在path数组中(在前向节点集合中前向查找)
public static ArrayList<Integer> getPath(int s,int t){
ArrayList<Integer> v=new ArrayList<>();
if(s==t) return v;
v.add(t);
int tmp=t;
while(tmp!=s){
tmp=prev_node[tmp];//tmp的前向节点
v.add(tmp);
}
Collections.reverse(v);
return v;
}
public static void set_link(ArrayList<Integer> p1,int s_node) {//p1:待检测的path,s_node:p1的末结点
int len=ans_path.size();
int len_p=p1.size();
for(int i=0;i<len;++i) {
ArrayList<Integer> p2=ans_path.get(i).path;
if(len_p>=p2.size()) continue;
boolean flag=true;
for(int j=0;j<len_p;++j) {
if(p1.get(j)!=p2.get(j)) flag=false;;
}
if(flag==false) continue;
//前面的path一样,然后就看扩展结点的了
for(int k=0;k<n;++k) {
if(k!=s_node&&edges[s_node][k]!=INF) {//从s_node扩展结点k
if(k==p2.get(len_p)) {//匹配成功,需将s_node->k 设置为INF
edges_tmp[s_node][k]=INF;
}
}
}
}
}
public static void recover_link(int s_node) {
for(int k=0;k<n;++k) {
edges_tmp[s_node][k]=edges[s_node][k];
}
}
public static void YEN_ksp(int s,int t,int k) {
if(s==t||k<=0) return;
//copy
for(int i=0;i<n;++i){
for(int j=0;j<n;++j){
edges_tmp[i][j]=edges[i][j];
}
}
Dijkstra(s);
Path p=new Path(getPath(s, t), dist[t]);//最短路,即迭代路径
while(k-->0) {
if(!ans_path.contains(p)) ans_path.add(p);
else continue;
int len=p.path.size();
ArrayList<Integer> path_tmp=new ArrayList<>();//p的部分迭代路径
for(int i=0;i<len-1;++i) {
path_tmp.add(p.path.get(i));
edges_tmp[p.path.get(i)][p.path.get(i+1)]=INF;
set_link(path_tmp,p.path.get(i));
Dijkstra(s);
edges_tmp[p.path.get(i)][p.path.get(i+1)]=edges[p.path.get(i)][p.path.get(i+1)];
recover_link(p.path.get(i));
if(dist[t]>=INF) continue;//没有路径了
Path pp=new Path(getPath(s,t),dist[t]);//修正后的最短路(偏离路径)
// if(!dev_path_sql.containsKey(pp)) {
// dev_path_queue.add(pp);
// dev_path_sql.put(pp.clone(), true);
// }
if(!dev_path_queue.contains(pp)) {
dev_path_queue.add(pp);
}
}
if(dev_path_queue.isEmpty()) break;
p=dev_path_queue.remove();
//dev_path_sql.remove(p);
}
}
public static void main(String[] args) {
Random r=new Random();
//先生成一个树
edges[0][1]=r.nextDouble()*10+10;
edges[1][0]=r.nextDouble()*10+10;
for(int i=1;i<n;++i) {
edges[i-1][i]=r.nextDouble()*10+10;
edges[i][i-1]=r.nextDouble()*10+10;
}
//在树上添加边
for(int i=0;i<n;++i) {
for(int j=0;j<n;++j) {
if(i==j) continue;
if(edges[i][j]>0) {//已经有边了
//edges[i][j]+=r.nextDouble()*50+50;
;
}else {//没边
if(r.nextDouble()>0) {//0.5的概率生成边
edges[i][j]=r.nextDouble()*100+500;
}else {
edges[i][j]=INF;
}
}
}
}
System.out.println("YEN_ksp start:");
YEN_ksp(0,55,50);//求出结点0到结点55的前50条最短路
int len=ans_path.size();
System.out.println(len);
for(int i=0;i<len;++i) {
int len_p=ans_path.get(i).path.size();
for(int j=0;j<len_p;++j) {
System.out.printf("%d ",ans_path.get(i).path.get(j));
}
System.out.printf("dist: %.3f\n",ans_path.get(i).dist);
}
// Dijkstra(1);
// for(int i=0;i<n;++i) {
// System.out.println(dist[i]);
// }
}
}
封装的path.java:
package lib.utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class Path implements Comparable,Cloneable{
public ArrayList<Integer> path;
double dist;
public Path(ArrayList<Integer> path,double dist){
this.path=path;
this.dist=dist;
}
@Override
public int compareTo(Object o) {
Path p=(Path)o;
if(this.dist>p.dist) return -1;
else if(this.dist==p.dist) return 0;
else return 1;
}
@Override
public int hashCode() {
return path.hashCode()+(int)dist;
}
@Override
public boolean equals(Object obj) {
Path p=(Path)obj;
if(this.dist!=p.dist) return false;
int len1=this.path.size();
int len2=p.path.size();
if(len1!=len2) return false;
for(int i=0;i<len1;++i) {
if(this.path.get(i)!=p.path.get(i)) return false;
}
return true;
}
@Override
protected Path clone(){
Path tmp;
try {
tmp=(Path) super.clone();
}catch(CloneNotSupportedException e) {
throw new RuntimeException("This calss not implement Cloneable");
}
tmp.path=(ArrayList<Integer>)this.path.clone();
tmp.dist=this.dist;
return tmp;
}
// public static void main(String[] args) {
// Map<Path, Boolean> dev_path_sql=new HashMap<Path, Boolean>();
// ArrayList<Integer> v1= new ArrayList<>();
// v1.add(1);
// v1.add(2);
// ArrayList<Integer> v2= new ArrayList<>();
// v2.add(1);
// v2.add(2);
// Path p1=new Path(v1,10);
// Path p2=new Path(v2,10);
// dev_path_sql.put(p1.clone(), true);
// System.out.println(dev_path_sql.containsKey(p2));
// p1.path.add(3);
// System.out.println(dev_path_sql.containsKey(p2));
// dev_path_sql.put(new Path(v2,10), true);
// System.out.println(dev_path_sql.size());
// dev_path_sql.remove(new Path(v2,10));
// System.out.println(dev_path_sql.size());
// }
}
运行结果如下:
YEN_ksp start:
50
0 1 55 dist: 521.530
0 53 54 55 dist: 563.921
0 56 55 dist: 567.054
0 57 56 55 dist: 567.667
0 59 58 57 56 55 dist: 573.120
0 52 53 54 55 dist: 574.652
0 50 51 52 53 54 55 dist: 586.064
0 58 57 56 55 dist: 589.331
0 55 dist: 590.887
0 54 55 dist: 613.887
0 47 48 49 50 51 52 53 54 55 dist: 626.572
0 60 59 58 57 56 55 dist: 644.148
0 49 50 51 52 53 54 55 dist: 649.717
0 51 52 53 54 55 dist: 652.126
0 65 64 63 62 61 60 59 58 57 56 55 dist: 656.914
0 48 49 50 51 52 53 54 55 dist: 676.458
0 63 62 61 60 59 58 57 56 55 dist: 687.190
0 62 61 60 59 58 57 56 55 dist: 688.555
0 61 60 59 58 57 56 55 dist: 689.318
0 44 45 46 47 48 49 50 51 52 53 54 55 dist: 694.788
0 64 63 62 61 60 59 58 57 56 55 dist: 695.797
0 66 65 64 63 62 61 60 59 58 57 56 55 dist: 703.198
0 42 43 44 45 46 47 48 49 50 51 52 53 54 55 dist: 705.473
0 46 47 48 49 50 51 52 53 54 55 dist: 723.243
0 45 46 47 48 49 50 51 52 53 54 55 dist: 734.643
0 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 738.297
0 43 44 45 46 47 48 49 50 51 52 53 54 55 dist: 740.333
0 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 749.203
0 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 dist: 761.700
0 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 769.119
0 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 dist: 780.547
0 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 784.978
0 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 793.785
0 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 800.300
0 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 dist: 802.399
0 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 810.460
0 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 dist: 813.899
0 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 818.640
0 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 dist: 829.547
0 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 831.752
0 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 dist: 832.565
0 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 dist: 836.453
0 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 841.897
0 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 dist: 852.720
0 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 867.588
0 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 dist: 874.265
0 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 876.927
0 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 880.461
0 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 dist: 884.851
0 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 dist: 886.536
另一个基于链表的实现,详见本人github: https://github.com/xycodec/CodeCraft_2019_public/blob/master/src/main/java/com/huawei/graph/Graph.java#L470