A* 启发式搜索
引言:
先前提到过的优先队列算法,每次都取出当前价值最小的值进行扩展,虽然已经大大的降低了复杂度,但也会有很多数据来卡你的复杂度;
比如说上面这个图,我们会一直沿着右边这条路搜索,
然而最后我们发现一直沿着这条边搜索下去答案会变得很大,
这几次搜索就没什么用了;
最终答案为12;
在这样的基础上,我们可以对未来可能产生的情况进行预估;
设计了一个“估价函数”;
表示从该状态到目标状态的估计的价值;
用f(x)来表示估价函数;
用g(x)表示实际上从该状态到目标状态的价值
astar 的算法核心就是从堆中不断取出 [f(x)+当前代价] 的最小值进行扩展;
定理:
对于每一个当前状态 x
有f(x)<=g(x)恒成立;
若不成立,正确答案无法得出;
举个例子:
上图的数字代表着当前节点的f()值,也就是估计值,以及边权;
我们以更新最后一个答案来举一个例子:
红色代表着当前代价;
这时的
队列为: 3+7,4+6,5+10
取出:3+7;
得到12+0;插入队列;
此时队列为:4+6,12+0,5+10;
取出:4+6;
得到:12+0;
再次取出时,取出的值是12+0;
此时目标节点第一次被取出应为答案;
ans=12;
然而事实上这道题根据优先队列来做应该是 8
为最左端的一条路径;
由此可见当估价函数被错误地估计到了大于实际价值时,
我们的正解实际上是被压在堆里出不来;
根据设计估价函数小于实际代价:
假如某非最优解路径上的某一点s先被扩展;
那么,当目标节点被取出来前的某一时刻:
1、s为非最优解,所以s的当前代价大于从 start 到 end 的最小代价 minn;
2、状态 t 为最优解路径上的一个状态,就有 t状态代价+f(t) <= t当前代价+g(t) =minn;
所以s当前代价>t当前代价+f(t),t状态就会比s先扩展,从而找到最优解;
A* 算法的实质:
带有估价的优先队列bfs;
这个题目的大意是:给了一个n个点,m个边的图,求start 到 end的第k短路长度;
一个比较直观的做法是优先队列bfs
当一个状态被第一次取出时:我们可以得到从出态到此状态的最小代价;
推论:当一个状态被第i次取出时,对应的代价是start 到 此状态的第i短路;
所以当目标节点被第k次取出时,我们就得到了答案
我们用优先队列极有可能会超时,
所以我们使用astar 算法;
按照f()的设计准则,f(x)要小于等于实际的x到end的第k短距离;
所以我们可以设计f为当前节点x到end 的最短路
然后进行优先队列算法,当第k次取出时,即为答案;
怎样记录节点被取出呢?
我们可以用一个二元组pair<int,int> 。这时c++stl自带的一个组,可以把他想象成一个两个元素的结构体;
然后优先队列也用二元组定义;
因为我们加上了一个估价函数,我们的实际时间复杂度被大大降低,快速求出结果。