POJ 2449 Remmarguts' Date [A*算法 K短路]

来源:互联网 发布:淘宝swot分析 编辑:程序博客网 时间:2024/06/06 02:39

题目链接: POJ 2449 Remmarguts’ Date
题目大意:

给一个图,求第k短的路。

输入格式:

第一行两个数 N(1<= N <= 1000), M(0 <= M <= 100000)。其中N表示顶点数, M表示边数。
接下来M行,每行三个数,a, b, t, 表示从a到b的边,权为k。
最后一行三个数, S, T, K, 表示让你求从S到T的第K短路径的长度

输出格式:

一个数, 第K短路径的长度

样例输入:

2 2
1 2 5
2 1 4
1 2 2

样例输出:

14

题解:

借着这题学一下A*算法。之前做过一题,求次短路(POJ 3255-Roadblocks [次短路 Dijkstra]),当时得知了A*可以求解K短路,不过没有深究。现在再返回来研究一下K短路的问题。

看了好多别人的总结,找到一系列非常棒的文章《关于寻路算法的一些思考》这是我看到的讲的最清晰的文章了。
这里还有一个A*算法可视化的Demo,也是在上面的文章里找到的。

先来看下Wikipedia对A*算法的定义:

A*算法

A*搜索算法,俗称A星算法。这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中的NPC的移动计算,或在线游戏的BOT的移动计算上。
该算法综合了Best-First Search和Dijkstra算法的优点:在进行启发式搜索提高算法效率的同时,可以保证找到一条最优路径(基于评估函数)。
在此算法中,如果以 g(n)表示从起点到任意顶点n的实际距离,h(n)表示任意顶点n到目标顶点的估算距离(根据所采用的评估函数的不同而变化),那么 A*算法的估算函数为:
f(n)=g(n)+h(n)
这个公式遵循以下特性:
如果g(n)为0,即只计算任意顶点n到目标的评估函数h(n),而不计算起点到顶点n的距离,则算法转化为使用贪心策略的Best-First Search,速度最快,但可能得不出最优解;
如果h(n)不为0,则一定可以求出最优解,而且h(n)越小,需要计算的节点越多,算法效率越低,常见的评估函数有——欧几里得距离、曼哈顿距离、切比雪夫距离;
如果h(n)为0,即只需求出起点到任意顶点n的最短路径g(n),而不计算任何评估函数h(n),则转化为单源最短路径问题,即Dijkstra算法,此时需要计算最多的定点;

定义看的不是很理解,可以看下《关于寻路算法的一些思考》,比很多讲得一知半解的博客强得多。


伪代码:

function A*(start,goal)     closedset := the empty set                 //已经被估算的节点集合         openset := set containing the initial node //将要被估算的节点集合     came_from := empty map     g_score[start] := 0                        //g(n)     h_score[start] := heuristic_estimate_of_distance(start, goal)    //h(n)     f_score[start] := h_score[start]            //f(n)=h(n)+g(n),由于g(n)=0,所以……     while openset is not empty                 //当将被估算的节点存在时,执行         x := the node in openset having the lowest f_score[] value   //取x为将被估算的节点中f(x)最小的         if x = goal            //若x为终点,执行             return reconstruct_path(came_from,goal)   //返回到x的最佳路径         remove x from openset      //将x节点从将被估算的节点中删除         add x to closedset      //将x节点插入已经被估算的节点         foreach y in neighbor_nodes(x)  //对于节点x附近的任意节点y,执行             if y in closedset           //若y已被估值,跳过                 continue             tentative_g_score := g_score[x] + dist_between(x,y)    //从起点到节点y的距离             if y not in openset          //若y不是将被估算的节点                 add y to openset         //将y插入将被估算的节点中                 tentative_is_better := true                  elseif tentative_g_score < g_score[y]         //如果y的估值小于y的实际距离                 tentative_is_better := true         //暂时判断为更好             else                 tentative_is_better := false           //否则判断为更差             if tentative_is_better = true            //如果判断为更好                 came_from[y] := x                  //将y设为x的子节点                 g_score[y] := tentative_g_score                 h_score[y] := heuristic_estimate_of_distance(y, goal)                 f_score[y] := g_score[y] + h_score[y]     return failure function reconstruct_path(came_from,current_node)     if came_from[current_node] is set         p = reconstruct_path(came_from,came_from[current_node])         return (p + current_node)     else         return current_node 




K短路

A*算法可以找到从原点S到终点T的一条最短路,然后结束算法。但A*算法同样也可以用来寻找K短路。
如果我们不设置closeset,A*算法的搜索空间里会有很多冗余的重复状态,但我们每次都只搜索距离终点最近的节点,所以如果终点可达,最终我们第一次搜索到终点T时,一定就是找到了一条最短路。当我们下一次又搜索到终点T时,一定有是当前的最优的路径,所以就是次短路……这样我们就可以得到我们要求的K短路了。
这题的难点也就是评估函数h(n)的设置。当评估函数h(n)恰好等于实际距离h*(n)时,A*算法是非常高效的。我们可以在给定图G的反向图G’中从T点执行一次Dijkstra,求得G’中T到所有其他节点的最短距离,就是图G中所有其他节点到T的最短距离,这恰好就是我们需要的h*(n)。
g(n)是从S到点n的最短距离,可以在A*算法的执行过程中逐步求得。
然后,我们就可以使用f(n)=g(n)+h(n)来给openset设置优先级,每次取出权最小的节点进行搜索,直到搜索到T点K次,算法结束。
如果T点是从S点不可达的,则不加限制的话算法将永远无法结束。我们需要找K短路,那么每个节点都不可能出现大于K次。所以当某个节点出现了大于K次时,就不要再该节点上继续扩展搜索空间了。

代码:

#include <iostream>#include <algorithm>#include <vector>#include <cstring>#include <queue>#define MAXN 10000#define INF 0x3f3f3f3fusing namespace std;typedef pair<int, int> P;int N, M, S, T, K;int dist[MAXN];int tdist[MAXN];int cnt[MAXN];bool f[MAXN];vector<P> Adj[MAXN];vector<P> Rev[MAXN];struct Edge {    int to, len;    Edge(){}    Edge(int t, int l):to(t), len(l){}};priority_queue<Edge> q;bool operator<(const Edge &a, const Edge &b) {    return (a.len + dist[a.to]) > (b.len + dist[b.to]);}void dijkstra() {    memset(dist, 0, sizeof(dist));    fill(tdist, tdist+MAXN, INF);    tdist[T] = 0;    while(!q.empty()) q.pop();    q.push(Edge(T, 0));    while (!q.empty()) {        int x = q.top().to;        int d = q.top().len;        q.pop();        if (tdist[x] < d) continue;        for (int i = 0; i < Rev[x].size(); i++) {            int y = Rev[x][i].first;            int len = Rev[x][i].second;            if (d+ len < tdist[y]) {                tdist[y] = d + len;                q.push(Edge(y, tdist[y]));            }        }    }    for (int i = 1; i <= N; i++){        dist[i] = tdist[i];    }}int aStar() {    if (dist[S] == INF) return -1;    while (!q.empty()) q.pop();    q.push(Edge(S, 0));    memset(cnt, 0, sizeof(cnt));    while (!q.empty()) {        int x = q.top().to;        int d = q.top().len;        q.pop();        cnt[x]++;        if (cnt[T] == K) return d;        if (cnt[x] > K) continue;        for (int i = 0; i < Adj[x].size(); i++) {            int y = Adj[x][i].first;            int len = Adj[x][i].second;            q.push(Edge(y, d+len));        }    }    return -1;}int main() {    std::ios::sync_with_stdio(false);    int a, b, t;    cin >> N >> M;    for (int i = 0; i < M; i++) {        cin >> a >> b >> t;        Adj[a].push_back(make_pair(b, t));        Rev[b].push_back(make_pair(a, t));    }    cin >> S >> T >> K;    //本题需要特别注意的地方,S==K时,也得走出去转一圈。。。- -!    if (S == T) K++;     dijkstra();    cout << aStar() << endl;    return 0;}
1 0
原创粉丝点击