现在的位置: 首页 > 综合 > 正文

Yen 算法

2013年08月15日 ⁄ 综合 ⁄ 共 2459字 ⁄ 字号 评论关闭

 1.2. Yen 算法

http://imlazy.ycool.com/post.1956603.html

    我从《The K shortest paths problem》这篇文章中学到了另一个算法,名叫 Yen 算法(Yen 是发明者的名字)。它和上面讲的典型的 A* 算法使用相同的启发函数,但是状态的含义以及扩展状态的方式不同。

    在 Yen 算法中,状态 x 不仅可以代表从 s 走到 x.v 的一条路径(记作 Psv),更代表了一条从 s 到 t 的完整的路径,也就是 Psv 再连接上 从 x.v 到 t 的最短路径。这一整条路径(记作 Px)的长度就是我们的启发函数 f(x)。

    在每个状态 x 中,还需要保存 x.v 在 Psv 中的前一个点,我们记作 x.pre。边 x.pre -> x.v 就称作 Px偏离边deviation edge); Px 上从 x.pre 到 t 的这一段子路径就称为 Px偏离路径deviation path)。为什么叫作偏离路径,看到后面都明白了。

    先求出从 s 到 t 的最短路径,它就是初始状态 x1 所要代表的路径。设它的第一条边是 s -> a,则  x1.v = a; x1.len = w(s, a) (w(s, a) 表示边 s -> a 的长度);  x1.pre = s,也就是说,规定 Px1 的偏离边是 s -> a。

    把 x1 放进优先队列。接下来,每当进入最大的循环的第 i 轮,从优先队列里出队的状态(启发值最小的,也就是路径长度目前最短的状态,记作 xi)就代表了第 i 短的解。第一轮出队的当然是前面定义的初始状态 x1。下面要从它发展新的状态,作为可能的第 2 短的解,放进优先队列。发展的方法如下:

    对于 Px1 的偏离路径上的每一条边(设它为 u -> v),都要找出另一条边 u -> v',满足在所有从点 u 出发的边当中, w(u, v') + dt(v') 仅仅高于 w(u, v) + dt(v) (或与它相同);也就是说,从 u 出发,走 u -> v 这条边到终点是最近的,走 u -> v' 这条边是第 2 近的(或者一样近)。从每一条 u -> v',我们都可以发展出一个新状态 x': x'.v = v'; x'.len = w(Psu) + w(u, v'); x'.pre = u,也就是说 Px' 的偏离边就是 u -> v'。

 

图 1

 

    图 1 给出了一个例子。假设图中蓝色和黑色的边组成的路径就是 Px1,蓝色边是它的偏离路径;那些红色的边就是前面说的那些 u -> v';红色的虚线就代表了从每个 v' 到 t 的最短路径。可见,每条 Px' 都是从 u -> v' 开始从 Px1 “身上”偏离出来的,因此把 从偏离边到终点 的这一段路径称为 Px'偏离路径

    注意,由于本问题中求的路径是可以带环的,所以走到终点以后还可以回头再走。因此,在图 1 中可以看到在点 t 后面也发展了一条偏离路径。这条偏离路径显然不再需要是第 2 短的,而是从 t 出发再回到 t 的最短的路径。

    上面讲的是从 x1 发展状态的情况。从之后的 xi 发展状态的时候还有一点要注意:在我们寻找偏离边 u -> v' 的时候,如果 u == xi.pre (也就是当 要找的偏离边 和 xi 的偏离边 是从同一点出发时),则要注意 u -> v' 不仅要和 u -> xi.v 不同,而且要和 xi 的所有祖先状态中从点 u 出发的那条边都不同,不然新发展的状态岂不是和 xi 的祖先状态重复了。

图 2

 

    图 2 给出了一个例子。假设蓝色路径是从黑色路径中发展出的偏离路径;当从蓝色路径发展偏离路径时,要找的是除了蓝色和黑色的边以外,能以最短的距离走到 t 的那条边,假设这里我们找到的是红色的那条边;当从红色路径发展偏离路径时,要找的是除了红色、蓝色和黑色的边以外,能以最短的距离走到 t 的那条边,假设这里我们找到的是绿色的那条边。

    如此一来,可能有很多偏离路径都是从同一点偏离出来的,但是它们的偏离边都不相同。要在程序中实现这一点,可以在每个状态中记录下所有祖先状态的偏离边。

    显然 Yen 算法也是一个 A* 算法,但是它有一个特点,前面已经说过了,就是最大的那个循环最多只要做 K 次,因为每当一个状态出队列时,我们就找到了一个解。因此基本上可以估计出算法的时间复杂度:

  • 设图中有 N 个点,那么一条偏离路径上最多只有 N 条边(因为它是一条边 加上 从某一点到终点的最短路径),也就是说,从一个状态最多发展出 N + 1 个新状态(偏离路径上的每条边发展出一个,从点 t 再发展出一个)。
  • 寻找一条偏离路径时,需要扫描从一个点出发的所有边,暂且假设从一个点出发的边最多也是 N 条,那么这一步要花 O(N) 的时间。
  • 可以想象优先队列(Open 表)里最多有 O(K * N) 个元素,所以每次维护优先队列的时间差不多是 O( lg (K * N) )。

    因此,总的时间复杂度,在最差情况下,差不多就是 O( K * ( N2 + lg(K * N) ) )。当然这只是我个人估计一下,不要太拿它当回事。

    1.3. MPS 算法

    同样是在《The K shortest paths problem》这篇文章中,还介绍了作者自已发明的 MPS 算法(MPS 是该文章的三位作者的名字缩写)。它的框架和 Yen 算法相同,但是有一个优化,可以加快寻找偏离边的速度。方法就是把从每个点出发的所有边,都按照从该条边走向 t 的最短距离 升序排序(最好用邻接链表描述图)。

图 3

    图 3 给出了一个例子。图中从点 s 出发的边有红、蓝和绿三条,延着它们到达终点 t 的最短距离分别为 3、 2 和 4。因此把从 s 出发的边排序为 (蓝, 红, 绿)。

    这样一来,寻找偏离边的时间就只有 O(1) 了。因为我们从某一点第一次发展偏离边时,只要选它的邻接链表中的第一条边;下一次再从该点发展时,只要选第二条边……再也不用一一扫描所有边了,也不用担心会和祖先状态的偏离边重复了。

    假设图中有 N 个点,从每个点出发的边最多也是 N 条。那么排序一个点的邻接链表需要 O(N * lg N) 的时间,排序整个邻接链表的时间就是 O(N2 * lgN);搜索的时间由 Yen 算法的 O(K * N2) 降至 O(K * N)。因此,整个算法在最差情况下的时间复杂度大约就是 O(N2 * lgN + K * N)。(从数字上看,好像也没有比 Yen 算法快到哪去……但是实际试下来确实是快的。)

抱歉!评论已关闭.