第K短路径问题

K K K 短路径(the K Shortest Path,KSP)问题是对最短路径问题的扩展。众所周知,使用 D i j s k t r a Dijsktra Dijsktra 算法能够确定最短路径,但是在某些情形下,我们除了想知道最短路径外,还想知道次短路径、第三短路径等等[1]。一个现实中的例子就是在使用“高德地图”的时候,需要从某个出发点到达一个目标点,在查询驾车路线时,“高德地图”一般会给出三条候选路径,一条路径具有最少的行车时间,一条具有最短的行车距离,另外一条是备用路线,这三条路径可能具有不同的路径长度,“高德地图”确定这些路径就是第 K K K 短路径问题的实际应用。

为了便于讨论,首先对 K S P KSP KSP 问题进行形式化的定义。令 P i P_i Pi 表示从起点 s s s 到终点 t t t 的第 i i i 短路径, K S P KSP KSP 问题是确定路径集合 P k = { p 1 P_k=\{p_1 Pk={p1 p 2 p_2 p2 p 3 p_3 p3 ⋯ \cdots p k } p_k\} pk},使得 P k P_k Pk 满足以下三个条件[2]:

(1) K K K 条路径是按次序产生的,即对于所有的 i i i i = 1 i=1 i=1 2 2 2 ⋯ \cdots K − 1 K-1 K1), p i p_i pi 是在 p i + 1 p_{i+1} pi+1 之前确定的;

(2) K K K 条路径是按长度由小到大排列的,即对于所有的 i i i i = 1 i=1 i=1 2 2 2 ⋯ \cdots K − 1 K-1 K1),均有 c ( p i ) < c ( p i + 1 ) c(p_i)<c(p_{i+1}) c(pi)<c(pi+1)

(3) K K K 条路径是最短的,即对于所有的 p ∈ ( P s t − P k ) p∈(P_{st}-P_k) p(PstPk),均有 c ( p k ) < c ( p ) c(p_k)<c(p) c(pk)<c(p)

某些情况下,可能并不需要上述严格的第 K K K 短路径,即可能并不满足第(2)个或者第(3)个条件,在算法中很容易实现,只需将路径长度的限制从原先的“不能相同”放松到“能够相同”即可。求解第 K K K 短路径有多种算法,以下介绍最为常用的 A ⋆ A^\star A 算法。

A ⋆ A^\star A 算法

求解第 K K K 短路径问题,主要使用的是 A ⋆ A^\star A 算法。 A ⋆ A^\star A 算法是一种启发式算法。在 《C++,挑战编程——程序设计竞赛进阶训练指南》 8 8 8 章“回溯法”的第 8.4 8.4 8.4 节“ 15 15 15 数码问题”中,已经对 A ⋆ A^\star A 算法做了介绍。在 A ⋆ A^\star A 算法中,对于每个状态 x x x,启发函数 f ( x ) f(x) f(x) 通常是以下的形式:

f ( x ) = g ( x ) + h ( x ) f(x)=g(x)+h(x) f(x)=g(x)+h(x)

其中 g ( x ) g(x) g(x) 表示从初始状态到达状态 x x x 时的代价, h ( x ) h(x) h(x) 表示从状态 x x x 到达目标状态时的估算代价。状态 x x x 包含两个域: u u u f x fx fx x . u x.u x.u 表示状态 x x x 所在的顶点, x . f x x.fx x.fx 表示状态 x x x 已经走过的路径长度,按照前述的约定可知 g ( x ) = x . f x g(x)=x.fx g(x)=x.fx

简便起见,先介绍如何解决有向图中的第 K K K 短路径问题,第 K K K 短路径中可以包含圈,但是有向图中不能出现负权圈(负权圈的出现可能导致最短路径失去意义,因为有可能通过无限次经过负权圈使得最短路径无限短)。典型的启发式搜索算法步骤如下:

(1)以终止顶点 t t t 为起点,使用 D i j k s t r a Dijkstra Dijkstra 算法确定 t t t 到其他顶点 u u u 的最短距离 d [ u ] d[u] d[u],这样在启发式搜索过程中,可以使用 d [ u ] d[u] d[u] 的值作为 h ( x ) h(x) h(x) 的参考值,此时 h ( x ) h(x) h(x) 的精确值 h ⋆ ( x ) = d [ x . u ] h^\star(x)=d[x.u] h(x)=d[x.u]

(2)状态 x x x 的初始值设置为: x . u = s x.u=s x.u=s x . f x = 0 x.fx=0 x.fx=0。根据启发式函数,有 f ( x ) = g ( x ) + h ( x ) = x . f x + d [ x . u ] = d [ x . u ] f(x)=g(x)+h(x)=x.fx+d[x.u]=d[x.u] f(x)=g(x)+h(x)=x.fx+d[x.u]=d[x.u],将状态 x x x f ( x ) f(x) f(x) 压入优先队列,将优先队列以 f ( x ) f(x) f(x) 为键值进行排序,具有较小 f ( x ) f(x) f(x) 值的状态排在队列的前端;

(3)从优先队列中取出位于队首的状态 x x x,根据图中从 x . u x.u x.u 出发的边扩展状态 y y y,得到 f ( y ) f(y) f(y),将状态 y y y f ( y ) f(y) f(y) 压入优先队列;

(4)如果队首状态 x x x 所在顶点 x . u x.u x.u 第一次到达目标顶点 t t t,则表明找到了一条从 s s s t t t 的最短路径,它的长度就是 f ( x ) f(x) f(x)。容易知道,当状态 x x x 第二次到达目标顶点 t t t 且当前的路径长度 x . f x x.fx x.fx 大于第 1 1 1 短路径的长度时,表明找到了次最短路径, ⋯ \cdots ,当第 K K K 次到达目标顶点 t t t x . f x x.fx x.fx 大于第 K − 1 K-1 K1 短路径的长度时,就找到了从 s s s t t t 的第 K K K 短路径。

const int MAXV = 1010, MAXE = 100010, INF = 0x3f3f3f3f;

int n, m;
int cnt, head[MAXV];
int dist[MAXV], visited[MAXV];

struct edge { int v, w, next; } edges[MAXE];
struct state
{
    int u, fx;
    bool operator < (const state &s) const { return fx > s.fx; }
};

void clearEdge()
{
    cnt = 0;
    memset(head, -1, sizeof head);
}

void addEdge(int u, int v, int w)
{
    edges[cnt] = edge{v, w, head[u]};
    head[u] = cnt++;
}

priority_queue<state> q;

int ksp(vector<tuple<int, int, int>> &data, int s, int t, int k)
{
    clearEdge();
    for (auto d : data) addEdge(get<1>(d), get<0>(d), get<2>(d));

    for (int i = 0; i < n; i++) dist[i] = INF, visited[i] = 0;
    int u = t;
    dist[u] = 0;
    while (!visited[u])
    {
        visited[u] = 1;
        for (int i = head[u]; ~i; i = edges[i].next)
            if (!visited[edges[i].v] && dist[edges[i].v] > dist[u] + edges[i].w)
                dist[edges[i].v] = dist[u] + edges[i].w;
        int least = INF;
        for (int i = 0; i < n; i++)
            if (!visited[i] && dist[i] < least)
                least = dist[i], u = i;
            
    }

    clearEdge();
    for (auto d : data) addEdge(get<0>(d), get<1>(d), get<2>(d));

    while (!q.empty()) q.pop();
    q.push(state{s, dist[s]});

    int lastPathLength = -INF;
    while (!q.empty())
    {
        state s = q.top(); q.pop();
        int fx = s.w - dist[s.u];
        if (s.u == t && fx > lastPathLength)
	{
            lastPathLength = fx;
            if (!(--k)) return fx;
	}
        for (int i = head[s.u]; ~i; i = edges[i].next)
            q.push(state{edges[i].v, fx + edges[i].w + dist[edges[i].v]});
    }
    return -1;
}

对于无向图来说,如果允许第 K K K 短路径出现圈,则可将一条无向边拆分为两条有向边,使用前述介绍的启发式搜索算法予以解决。如果第 K K K 短路径不允许出现圈,应该如何处理呢?此时需要在状态 x x x 中增加域,用于记录该状态已经经过的顶点,在扩展状态时,对于后续顶点,如果某个顶点是已经经过的顶点,则不能将此顶点作为后续顶点进行扩展。当路径上的顶点数目较小时,例如不大于 64 64 64 个,则可以使用一个 l o n g   l o n g   i n t long\ long\ int long long int 型整数,以一个位来表示某个顶点是否在路径上,通过位运算来判定某个顶点是否是已经经过的顶点。不过此种方法存在局限,不能处理路径上具有较多顶点的情形。

Y e n Yen Yen 算法

Y e n Yen Yen 算法也是一种 A ⋆ A^\star A 算法,它所使用的启发函数与典型的 A ⋆ A^\star A 算法相同,但是在状态的含义即后续状态的扩展上与典型的 A ⋆ A^\star A 算法不同。 Y e n Yen Yen 算法相较于典型的 A ⋆ A^\star A 算法具有更优的时间复杂度[3]。

该算法的核心思想以最短路径 P 1 P_1 P1 或者已经求得的第 i i i 条最短偏离路径 P i P_i Pi 为基础,在 P i P_i Pi 上除了终止节点外的其他节点中确定偏离节点,构造候选路径,从候选路径集中找到最短的一条为最短偏离路径 P i + 1 P_{i+1} Pi+1。不同的偏离路径算法确定偏离节点和偏离路径的方法不同。

该类算法中目前使用最广泛的算法是 Y e n Yen Yen 在 1971 年提出的以其名字命名的 Y e n Yen Yen 算法[4]。该算法在求 P i + 1 P_{i+1} Pi+1 时,要将 P i P_i Pi 上除了终止节点外的所有节点都视为偏离节点,并计算每个偏离节点到终止节点的最短路径,再与之前的 P i P_i Pi 上起始节点到偏离节点的路径拼接,构成候选路径,进而求得最短偏离路径。网络图中的节点数设为 n n n,边数设为 m m m,所求得路径数设为 K K K, 其时间复杂度为 O ( k n ( m + n l o g n ) ) O(kn(m+nlogn)) O(kn(m+nlogn)) Y e n Yen Yen 算法的优点是易于理解,可以准确地找到图中任意两节点间的 K K K 条最短路径,缺点是时间复杂度较高,时间代价较大,主要原因是在求 P i + 1 P_{i+1} Pi+1 时,要将 P i P_i Pi 上除了终止节点外的所有节点都视为偏离节点,从而在选择偏离节点发展候选路径时占用了大量的时间。

为提高运行速度,后人在此算法的基础上不断改进和发展。比较有效的算法之一是 M a r t i n s Martins Martins 1999 1999 1999 年提出的 M P S MPS MPS 算法[5],其特点是简化了偏离路径的长度计算,在生成候选边时不像 Y e n Yen Yen 算法那样计算每条候选路径的长度,而是要求更新每条弧上的减少长度,只选择长度减少最小的弧作为最短偏离路径,该算法在一般情况下可以提高运行速度,但是在最差的情况下与 Y e n Yen Yen 算法的时间复杂度一样。

强化练习:10740 Not the Best

参考:

(1)https://blog.csdn.net/pi9nc/article/details/12256019

(2)https://www.jianshu.com/p/ea0e6894259b

(3)付媛, 朱礼军, 韩红旗. K最短路径算法与应用分析[J]. 情报工程, 2015, 1(1): 112-119.

(4)Yen J Y. Finding the K Shortest Loopless Paths in a Network[J]. Management Science, 1971, 17(11): 712-716.

(5)Martins E Q V, Pascoal M M B. A new implementation of Yen’s ranking loopless pathsalgorithm[J]. Quarterly Journal of the Belgian, French and Italian Operations Research Societies, 2003, 1(2): 121-133.

  • 12
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值