• 对于 前K短路径问题 和 A*算法 的一些小小总结


       

     
    首先,为了说话方便,列出一些术语:

    启发式搜索中,对于每个状态 x,启发函数 f(x) 通常是这样的形式:

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

    其中 g(x) 是从初始状态走到 x 所花的代价;h(x) 是从 x 走到目标状态所需要的代价的估计值

    相对于 h(x),还有一个概念叫 h*(x),表示从 x 走到目标状态所需要的实际最小代价(当然,这个值有时我们是事先无法知道的)。

    如果在你的启发函数里,能保证 h(x) <= h*(x),也就是说,你不能高估了从 x 走到目标状态所需要的代价,那就可以说这个搜索是 A* 算法(这里的“*”,英文就读作 star)。

    A* 算法的特点是,如果存在从初始状态走到目标状态的最小代价的解,那么用 A* 算法搜索时,第一个找到的解就一定是最小代价的。这就是所谓的可采纳(admissible)。

    1. 求前 K 短的 可以带环的 路径(的长度)

    1.1. 典型的启发式搜索

    设起点为 s;终点为 t;对于一个点 v,dt(v) 表示从 v 走到 t 的最短路径的长度(可以在初始化的时候全都算好)。

    网友 richard 教会了我,可以用最典型的启发式搜索来解这个问题。一个状态 x 表示的是从 s 走到某个点的一条路径,把这个点记作 x.v,把这条路径的长度记作 x.len。接着,我们可以使用以下启发函数:

    g(x) = x.len; h(x) = dt(x.v);
    ∴ f(x) = g(x) + h(x) = x.len + dt(x.v)

    初始状态中, x.v = s; x.len = 0。然后每次让优先队列(所谓的 Open 表)中 f(x) 值最小的状态 x 出队,再跟据图中所有从 x.v 出发的边发展下一层状态,让它们进队列。优先队列中不存在判重复的问题,因为每个状态所代表的路径肯定是不一样的。

    不难想通,这是一个 A* 算法,因为这里的 h(x) 本身就是 h*(x),当然满足 h(x) <= h*(x)。因此可以说,在每次出队列的状态 x 中,第一次遇到 x.v == t 时,就找到了从 s 到 t 的第一短的路径,它的长度就是 f(x)……第 k 次遇到 x.v == t 时,就找到了从 s 到 t 的第 k 短的路径。

    1.2. Yen 算法

    我从《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 算法快到哪去……但是实际试下来确实是快的。)

    2. 求前 K 短的 无环 路径(的长度)

    2.1. 典型的启发式搜索

    网友 richard 在他的这篇文章里介绍了,把 1.1 节中的算法稍加修改,就可以用来求无环的前 K 短路径。修改方法就是在每个状态中保存 Psv 所经过的点;当从一个状态发展新状态时,下一步走的点不能出现在 Psv 中(如果点比较少的话,用位运算就可以很快地对此进行判断。)。这样一来,最终求出的路径就无环了。



    图 4
    这个算法在大多数情况下确实很好用,但是在 2006 年横滨赛区的最后一题中,就有一组阴险的数据可以让这个算法超时。如图 4 所示,从点 s 出发只有两条边:蓝色的很短,红色的非常长(既使把图中所有边的长度都加起来,也没有它长);能走向点 t 的只有一条边,它的起点正是蓝色边的终点;图的其它部分有很多点,它们两两之间都有边(图 4 中只是象征性地画了一下,实际上有更多点)。可以想象,只要第一步走了蓝色的边,那么能到达点 t 的无环的路径只有一条,那就是 s -> 蓝色的点 -> t。从第 2 短的解开始,都必须走红色的边。

    但是启发式搜索一定会先走蓝色的边,然后尝试其后的所有路径。直到实在走投无路径时,才会回过头来走红色的边,因为从长度来看,红色边的优先级实在太低了(虽然它才是正解)。假设图中有 N 个点,可以想象,启发式搜索会先尝试 O(N!) 条错误的路径,那就太可怕了。

    我曾经想过下面一些优化的方案,但是好像都行不通:
    • 如果在一个我们想要发展的新状态 x 中,从 s 到 x.v 的路径 和 从 x.v 到 t 的最短路径上有重复的点(前者的点集被保存在 x 中;后者的点集可以在初始化所有最短路径时记录下来),则不让它进优先队列(Open 表)?

      这样做是不对的。因为虽然从 x.v 到 t 的最短路径不能构成无环的解,但是这并不代表从 x.v 到 t 的稍长一点的路径就不可能构成无环的解。因此,这样的状态还是必须得发展下去,否则就可能错过了一些解。

    • 在优先队列(Open 表)里只保存前 K 个最优的状态,比较差的状态就不进队列了?

      这样做更加是不对的。因为图 4 这个例子就已经明确地说明了,启发值比较优先的状态并不一定能通向正解,而启发值较差的状态说不定就能通向正解。
    总之,目前在我看来,典型的启发式搜索对于图 4 中的这种情况真的是没辙。当然,我希望 richard 能够反驳这个论点。

    2.2. Yen 算法

    Yen 算法的无环版本我在这篇文章里已经写过了。其思想和它的可以带环的版本相同,只是在找偏离路径的时候,不能再用初始化求好的现成的最短路径了,因为它们可能无法构成无环的解;而是要当场求一条最短路径,在求的过程中屏蔽掉前半条路径经过的点,以保证整条路径无环。

    由于 Yen 算法的无环版本在找偏离路径时,不再是扫描从一个点出发的所有边,而是运行一次最短路径算法。所以它的最差时间复杂度 由可以带环版本的 O( K * ( N2 + lg(K * N) ) ) 升至 O( K * ( N3 + lg(K * N) ) )

    2.3. MPS 算法

    作者还是 M、 P 和 S 这三个人,在《The K shortest loopless paths problem》这篇文章中介绍了 MPS 算法的无环版本。它和 MPS 算法的可以带环版本基本相同,只是在最大的循环中,每当一个状态出队列时,判断它是否无环,如果无环才算找到一个解。当然,不管出队的状态有没有环,都需要从它发展新状态,原因在 2.1 节中已经说过了。

    这个算法在一般情况下(比如随机生成的图)会比 Yen 算法的无环版本快很多,毕竟它在寻找偏离路径上有很大的时间优势。但是它的致命伤和典型的启发式搜索一样,就是像图 4 那样的情况。因为它在决定偏离路径时,还是以启发为主,并不能确定找到的是正解,我就不多说了。

    就写到这里吧。最后总结一下,上面介绍的求前 K 短路径的各种算法,不管是有环的版本还是无环的版本,都是 A* 算法。正因为这样,它们才能保证能依次求出前 K 短的解。最后面好像写得有点潦草,但是我觉得足以说明问题了。跟本文有关的题目,我知道的还有 UVA 10740 PKU 2449


    相关文章:

    转载地址:http://imlazy.ycool.com/post.1956603.html

  • 相关阅读:
    Martix工作室考核题 —— 打印一个菱形
    Martix工作室考核题 —— 打印一个菱形
    Martix工作室考核题 —— 打印九九乘法表
    Martix工作室考核题 —— 打印九九乘法表
    Martix工作室考核题 —— 打印九九乘法表
    Martix工作室考核题 —— 201938 第三题
    Martix工作室考核题 —— 201938 第三题
    Martix工作室考核题 —— 201938 第三题
    Martix工作室考核题 —— 201938 第一题
    fiddler模拟发送post请求
  • 原文地址:https://www.cnblogs.com/LUO257316/p/3220859.html
Copyright © 2020-2023  润新知