A*算法是一类贪心算法,其可以用于寻找最优路径。我们可以利用A*算法来求第k短路径。
一条路径可以由两部分组成,第一部分是一个从出发到达任意点的任意路径,而第二部分是从第一部分的末端出发,到终点的最短路径。两部分正好可以组成一条路径,且每一条路径都可以分解这两部分(允许任意一部分为空)。因此当我们已知第一部分的路径A时,设第二部分为B,我们可以尝试预估完整的路径A+B的费用(距离),我们将公式定义为:f(A)=g(A)+h(A)。其中g(A)表示第一部分A的已知长度,而h(A)表示路径A到终点的预估最短距离,而f(A)表示路径A的预估总费用。由于A*是贪心算法,因此我们每次都会选择预估总费用最低的路径,并在进行拓展。
下面说明如何用A*算法求解第k短路径。首先我们需要计算从所有结点到终点的最短路径(以终点为起点逆向跑最短路算法)。之后我们维护一个最小堆,在一开始将起点代表的路径加入堆中。我们对路径的评估采用A*的方式,由于每个结点到终点的距离已知,因此h(A)也是确定而非预估的。每次都从堆中取预估费用最小的路径并在其上进行拓展(访问相邻的所有结点并创建新的路径加入堆中,允许新路径上出现重复结点),并将最小路径从堆中移除。在上面过程中我们每次发现路径的末尾是终点,则进行一次计数,直到计数为k,则我们找到了第k短路径。
由于采用的是BFS方式扩展路径,因此可以保证存储在堆中的路径都是不同的。每次找到的最短路径必然是所以以残留在堆中的路径为前缀的从起点到终点的最短路径,可以知道依序找到的路径1,2,...其费用必定非严格递增。而第一次找到的路径显然是最短的。若前n-1次找到的路径是前n-1短的,我们可以保证第n短的路径的某个前缀保留在堆中。若第n短的路径与前n-1条找到的最短路径的最长相同前缀均为0,那么可以保证这个前缀必然是未被消耗的(所有的路径的公共前缀起点在一开始就加入了最小堆中)。而若第n短的路径与第t短路径有最长前缀p,那么两条路径t与路径n的第p+1个结点互不相同,但是由于计算第t短路径时,我们会将路径n的p+1长前缀创建并加入堆中,且一直到第n-1条路径出堆,该p+1长前缀都不会出堆,所以此时该前缀依旧存在于最小堆中。故下一次找到的路径是第n短的路径。
下面说明时间复杂度,利用Dijkstra算法可以在O(|E|log2(|V|))时间复杂度内计算最短路径。之后我们计算每次从堆中弹出完整路径(从起点出发抵达终点的路径)过程中最多有|V|条路径被弹出(最短路径不含环),且这过程中最多有|E|个结点被加入堆中。故堆中最多含有k|E|条路径,因此总的时间复杂度为O(k*(|V|+|E|)log2(k|E|))+O(|E|log2(|V|))=O(k|E|log2(k|E|))。