最短路Dijkstra算法的一些扩展问题
很早以前写过关于A*求k短路的文章,那时候还不明白为什么还可以把所有点重复的放入堆中,只知道那样求出来的就是对的。知其然不知其所以然是件容易引发伤痛的事啊,前天一次pku的比赛就得到了应验,因此下决心把这一类的问题要好好想一想。
搞了一天,总算有点成果,在此就总结一下这几类问题:
- 两点间的最短路的条数;
- 多关键字的极短路问题;
- 给定长度的路的条数;
- K短路及其条数;
- 标号法的一些看法;
- 程序设计与实现。
在此,先说明几个符号:
label 是每次出堆的一个标号;
struct label
{
int vertex; // 该标号所代表的顶点
int val; // 标号的值
};
phi(x):顶点x的标号,即源到该点的最短路
f(x):源到x的路径条数
S和T:分别是源和汇
int vertex; // 该标号所代表的顶点
int val; // 标号的值
};
phi(x):顶点x的标号,即源到该点的最短路
f(x):源到x的路径条数
S和T:分别是源和汇
问题1
当用顶点s来作更新时,设对某条边s->t长为v:
if phi(s)+v==phi(t) then f(t)+=f(s)
else if phi(t)>phi(s)+v then f(t)=f(s)
不用再多说了吧,这不是今天的重点,略过。
if phi(s)+v==phi(t) then f(t)+=f(s)
else if phi(t)>phi(s)+v then f(t)=f(s)
不用再多说了吧,这不是今天的重点,略过。
问题2,
多关键字的问题也是很具有实际意义的,就以双关键字为例子,其它的可以扩展。比如交通网络中的路一般都有收费和用时两个参量,在这种情况 下,就不存在一个最的定义,相应的,若用(cost,time)来表示一个标号,那么显然标号不再是一个全序而是偏序。在堆中比较两个标号可以采用第一、 第二关键字分别比较的方法,因此每次出堆的一定是一个极小的标号。由于每个顶点可以存在多个标号,因此对每个点维护一个标号的集合。如果把这个集合排序的 话,那么一定是按cost严格递增,同时按time严格递减。
问题3
,若要求的长度是L,设L*=phi(T),那么delta=T-T* ,即对图中的每个点来说,可以维持一个delta的冗余度,在这样的长度下都是有希望最后到达T的长度是L。这只是一个必要条件,能否进一步限定?设T到 图中任意点x的最短距离为lambda(x),那么对某个点x,只有标号同时满足:
1.label.val-phi(x)<=delta;
2.label.val+lambda(x)<=L 时才有希望。
可以发现,式子中的lambda就是用作A*的启发函数,因此我们把问题3就归结到了问题4,下面将一起处理。
问题4,这是本文的核心,我将详细说明。
1.label.val-phi(x)<=delta;
2.label.val+lambda(x)<=L 时才有希望。
可以发现,式子中的lambda就是用作A*的启发函数,因此我们把问题3就归结到了问题4,下面将一起处理。
问题4,这是本文的核心,我将详细说明。
我们知道A*对空间的要求是很高的,如果用扩展dijkstra的话则解收敛的更慢,因此更无法接受。如何优化扩展的顶点的数量就成了要亟待解决的关键问题。
定义: 将上面的delta我们称为冗余度,把每个点x分裂成独立的x+delta个顶点,那么这样形成的新图成为层次图。
用G’代表该层次图,那么G’的形态是怎样的呢?假想把原图G复制delta份按0,1,2顺序依次画出来,那么x+k所在的图就是第k层图。相应 扩展其他概念,用phi(x,k)表示从(S,0)到(x,k)的最短距离,f(x,k)表示到达(s,k)的最短路数量。对某条边(s->t, v)来说,若phi(s,k)+v==phi(t,k),即该边连接的点在同层,否则一定有phi(s,k)+v==phi(t,k’),k’> k,因此顺着这条边走就到达了下一层。
有了这个概念,不难知道我们要求的就是f(T,delta),即从(S,0)->(T,delta)的最短路的数量,化为我们已解决的问题1。但这还没完,层次图毕竟是个概念,在实现中要怎么解决呢?继续看下文。
要注意的是 (S,k),k>0是不存在的,这很容易理解。
问题5,标号法的实质其实可以归结为如下的定理:
对于一个求最大值的问题,如果能用标号法解决,当且仅当未知问题->未知问题的解决时间非负,即满足单调性。若要求解的数量,解决时间必须为正,即严格单调性。
这个性质很有意思,它只要求未知问题间的解决时间非负,因此联想到dijkstra算法,由于开始的时候S是已知问题,所以从S连出去的边是可以为负的,这在很多的书中都没有提到,因此很多人对dijkstra的理解也就停留在了机械记忆的阶段。
好了,
对于一个求最大值的问题,如果能用标号法解决,当且仅当未知问题->未知问题的解决时间非负,即满足单调性。若要求解的数量,解决时间必须为正,即严格单调性。
这个性质很有意思,它只要求未知问题间的解决时间非负,因此联想到dijkstra算法,由于开始的时候S是已知问题,所以从S连出去的边是可以为负的,这在很多的书中都没有提到,因此很多人对dijkstra的理解也就停留在了机械记忆的阶段。
好了,
最后就是编程的问题了,我用代码来表示比较清晰。
Run dijkstra in the 0’th level,那么phi(x,0)就是已知的了。
while ( heap is not empty )
Run dijkstra in the 0’th level,那么phi(x,0)就是已知的了。
while ( heap is not empty )
{
Get a minimal label From min-heap
For any edge along go outside this label , (s->t,v):
length=label.val+v;
k=label.val-phi(s,0);
theta=length-phi(t,0);
if ( theta <= delta )
Get a minimal label From min-heap
For any edge along go outside this label , (s->t,v):
length=label.val+v;
k=label.val-phi(s,0);
theta=length-phi(t,0);
if ( theta <= delta )
{
f(t,theta)+=f(s,k);
if (t,theta) is not in heap
f(t,theta)+=f(s,k);
if (t,theta) is not in heap
then Insert it into heap
}
}
从代码中我们也看出,其实编程中并没用到phi(x,k),k>0,所以其实保留phi(x)就可以了。
最后计算下复杂度。|V’|<=delta*|V|,|E’|<=delta*(|E|+|V|^2),所以总复杂度O(delta*V^2*(lgV+lg(delta)),这实际上也是在以前的文章中遗留的用A*求K短路的复杂度上限。
好了,问题都解决了。可是仍然不够圆满,因为新问题又来了,如果delta比较大该怎么做呢?还是有办法,可以利用K短路算法解决。
}
}
从代码中我们也看出,其实编程中并没用到phi(x,k),k>0,所以其实保留phi(x)就可以了。
最后计算下复杂度。|V’|<=delta*|V|,|E’|<=delta*(|E|+|V|^2),所以总复杂度O(delta*V^2*(lgV+lg(delta)),这实际上也是在以前的文章中遗留的用A*求K短路的复杂度上限。
好了,问题都解决了。可是仍然不够圆满,因为新问题又来了,如果delta比较大该怎么做呢?还是有办法,可以利用K短路算法解决。