第k短路问题及其应用(POJ2449 Remmarguts' Date)
(转载时请注明原地址http://www.cnblogs.com/n-u-l-l/archive/2012/07/29/2614194.html)
在一个有权图中,从起点到终点最短的路径成为最短路,第2短的路成为次短路,第3短的路成为第3短路,依此类推,第k短的路成为第k短路。那么,第k短路怎么求呢?
对于第k短路,可以想到的一个比较朴素的算法就是广度优先搜索,使用优先队列从源点s进行广搜,当第k次搜索到终点t时,所的长度即所求但是这种方法在运行过程中会产生特别多的状态,当图比较简单、k比较小时,可以一试,但是当k较大或者图中点数较多时,会面临爆栈的危险。目前使用比较多的算法是单源最短路配合A*。A*是搜索中比较高级的方式,A*算法结合了启发式方法(这种方法通过充分利用图给出的信息来动态的作出决定而使搜索次数大大降低)和形式化方法(这种方法不利用图给出的信息,而仅通过数学的形式分析,如Dijkstra算法)。它通过一个估价函数f(h)来估计图中的当前点p到终点的距离,并由此决定它的搜索方向,当这条路径失败时,它会尝试其他路径。对于A*,估价函数=当前值+当前位置到终点的距离,即f(p)=g(p)+h(p),每次扩展估价函数值最小的一个。对于第k短路算法来说,g(p)为从源点s到当前点p所走的路径长度,h(p)为从当前点p到终点t的最短路,因此f(p)的意义就是从s按照当前路径经过p点后到达t的总距离。也就是每次扩展都是有方向的,这样无论对提高出解的速度还是降低扩展的状态数目都是有好处的。为了加快计算,h(p)需要在搜索之前进行预处理,只要将原图的所有边反向,再从终点t做一次单源最短路即可得到h(p)。单源最短路求法有Dijkstra,Bellman-Ford,SPFA等。
具体步奏:
这里我们使用链式前向星来存储如图,由于需要预处理所有点到终点的最短路,就需要将图G中所有边反向得到图G',再从终点t做一次单源最短路,所以实际上就是两张图。
(1)将有向图的所有边反向(无向图可以省略此步),以原图终点t为源点做一次单源最短路,结果记入数组dis[i]中,dis[i]即为原图中点i到点t的最短距离。这里的dis[i]即上述的h(p);
(2)新建一个优先队列,将源点s加入到队列中;
(3)从优先队列中弹出f(p)最小的点p(这里如果存在f(p)相等的点,则弹出g(p)最小的点),如果点p就是终点t,则计算t出队列的次数,如果当前为t的第k次出队,则当前路径长度就是s到t的第k短路,算法结束;否则遍历与p相连的所有的边,将扩展出的到p的邻接点信息加入到优先队列。
值得注意的是,当s==t时需要计算(k+1)短路,因为s到t这条距离为0的路不能算在这k短路中,这时只需将k自增1后再求第k短路即可。
下面举一个采用链式前向星存储图,用Dijkstra加优先队列优化求单源最短路和A*算法计算第k短路的例子。POJ第2449题,题目链接:http://poj.org/problem?id=2449
Remmarguts' Date
Time Limit: 4000MS Memory Limit: 65536K
Total Submissions: 14975 Accepted: 4078
Description
"Good man never makes girls wait or breaks an appointment!" said the mandarin duck father. Softly touching his little ducks' head, he told them a story.
"Prince Remmarguts lives in his kingdom UDF – United Delta of Freedom. One day their neighboring country sent them Princess Uyuw on a diplomatic mission."
"Erenow, the princess sent Remmarguts a letter, informing him that she would come to the hall and hold commercial talks with UDF if and only if the prince go and meet her via the K-th shortest path. (in fact, Uyuw does not want to come at all)"
Being interested in the trade development and such a lovely girl, Prince Remmarguts really became enamored. He needs you - the prime minister's help!
DETAILS: UDF's capital consists of N stations. The hall is numbered S, while the station numbered T denotes prince' current place. M muddy directed sideways connect some of the stations. Remmarguts' path to welcome the princess might include the same station twice or more than twice, even it is the station with number S or T. Different paths with same length will be considered disparate.
Input
The first line contains two integer numbers N and M (1 <= N <= 1000, 0 <= M <= 100000). Stations are numbered from 1 to N. Each of the following M lines contains three integer numbers A, B and T (1 <= A, B <= N, 1 <= T <= 100). It shows that there is a directed sideway from A-th station to B-th station with time T.
The last line consists of three integer numbers S, T and K (1 <= S, T <= N, 1 <= K <= 1000).
Output
A single line consisting of a single integer number: the length (time required) to welcome Princess Uyuw using the K-th shortest path. If K-th shortest path does not exist, you should output "-1" (without quotes) instead.
Sample Input
2 2
1 2 5
2 1 4
1 2 2
Sample Output
14
下面为参考源代码,搜索过程中的数据采用Node类型来保存,由于存储了两张图,则分别用adj1和edge1来存储原图,用adj和edge来存储反向图。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <limits.h> 4 #include <queue> 5 using namespace std; 6 7 struct Edge 8 { 9 int to; 10 int value; 11 int next; 12 bool operator < (const Edge &t) const 13 { 14 return t.value < value; 15 } 16 }; 17 struct Edge1 18 { 19 int to; 20 int value; 21 int next; 22 }; 23 struct Node 24 { 25 int to; 26 int f, g; 27 bool operator < (const Node &t) const 28 { 29 if(t.f==f) 30 return t.g < g; 31 return t.f < f; 32 } 33 }; 34 Edge edge[100010]; //存储边与原图反向的图 35 Edge1 edge1[100010]; //存储原图 36 int adj[1010], adj1[1010], visited[1010], dis[1010], edgeNum, edgeNum1; 37 int N, M; 38 39 void Dijkstra(int start) 40 { 41 int k; 42 Edge t, cur; 43 priority_queue<Edge> PQ; 44 for(k=0; k<N; k++) 45 { 46 visited[k] = 0; 47 dis[k] = INT_MAX; 48 } 49 t.to = start; //起始顶点 50 t.next = -1; 51 t.value = 0; 52 dis[start] = 0; //自己到自己路径为0 53 PQ.push(t); 54 visited[start] = 1; //标记以入队 55 while(!PQ.empty()) 56 { 57 cur = PQ.top(); //出队 58 PQ.pop(); 59 visited[cur.to] = 0; //标记出队 60 for(int tmp = adj[cur.to]; tmp != -1; tmp = edge[tmp].next) 61 { 62 if(dis[edge[tmp].to] > dis[cur.to] + edge[tmp].value) 63 { 64 dis[edge[tmp].to] = dis[cur.to] + edge[tmp].value; 65 if(visited[edge[tmp].to] == 0) 66 { 67 PQ.push(edge[tmp]); 68 visited[edge[tmp].to] = 1; 69 } 70 } 71 } 72 } 73 } 74 75 int A_star(int start, int end, int k) 76 { 77 Node e, ne; 78 int cnt = 0; 79 priority_queue<Node> PQ; 80 if(start==end) 81 k++; 82 if(dis[start]==INT_MAX) //无法到达终点 83 return -1; 84 e.to = start; 85 e.g = 0; 86 e.f = e.g + dis[e.to]; 87 PQ.push(e); 88 while(!PQ.empty()) 89 { 90 e = PQ.top(); 91 PQ.pop(); 92 if(e.to==end) 93 cnt++; //第cnt短路 94 if(cnt==k) 95 return e.g; 96 for(int i=adj1[e.to]; i!=-1; i=edge1[i].next) 97 { 98 ne.to = edge1[i].to; 99 ne.g = e.g + edge1[i].value; 100 ne.f = ne.g + dis[ne.to]; 101 PQ.push(ne); 102 } 103 } 104 return -1; 105 } 106 107 void addEdge(int a, int b, int len) //反向图添加边 108 { 109 edge[edgeNum].to = b; 110 edge[edgeNum].next = adj[a]; 111 edge[edgeNum].value = len; 112 adj[a] = edgeNum++; 113 } 114 115 void addEdge1(int a, int b, int len) //原图添加边 116 { 117 edge1[edgeNum1].to = b; 118 edge1[edgeNum1].next = adj1[a]; 119 edge1[edgeNum1].value = len; 120 adj1[a] = edgeNum1++; 121 } 122 123 int main() 124 { 125 // freopen("test.txt", "r", stdin); 126 int a, b, len, i, s, t, k; 127 while(scanf("%d %d", &N, &M)!=EOF) 128 { 129 for(i=0; i<N; i++) 130 { 131 adj[i] = -1; 132 adj1[i] = -1; 133 } 134 for(edgeNum=i=0; i<M; i++) 135 { 136 scanf("%d %d %d", &a, &b, &len); 137 addEdge1(a-1, b-1, len); //构造原图 138 addEdge(b-1, a-1, len); //构造反向图 139 } 140 scanf("%d %d %d", &s, &t, &k); 141 Dijkstra(t-1); //求原图中各点到终点的最短路 142 int ans = A_star(s-1, t-1, k); //求第k短路 143 printf("%d\n", ans); 144 } 145 return 0; 146 }
代码提交后反馈信息为: