A*大意:利用一个估价函数h()确定点的遍历和更新顺序从而减少遍历次数
IDA*大意:通过贪心取答案步数,并用于与估价函数剪枝,逐步验证得出正确答案。
可能看起来有点迷,其实并不难,下面将用谁都不懂通俗易懂的方式讲解。
特点是都常用于骗分。
前置芝士:最短路和深搜
相信大家都会。
算法:
1. A*
感觉并不是很需要图啊。。。
设起点到终点的总费用为f(n),从起点到当前点的已知费用为g(n),预估当前点到终点的费用为h(n)。
显然我们可以预估f(n)=g(n)+h(n),便得到了一个总费用的近似值(先不考虑正确性)。
得到近似值之后,便可以加入一个小根的优先队列。明显总费用越小越好,所以先遍历预估总费用小的点。
第k次到达终点,便是k短路。
代码表示:(c++演示
struct data{
int g,h;
bool operator <(const data &v)const{
return g+h>v.g+v.h;
}
}
priority_queue<data>q;
注意到当前点到终点的费用是预估而不是精确值,与广搜等遍历找最小算法不同,它体现了启发性,所以是启发式搜索。
该算法保证正确性的条件是h(n)<=h*(n),其中h*(n)为当前点到终点的确定的最小费用,h(n)>=0。
证明:它的最优性质体现在当到达终点时,h(n)<=h*(n)=0,那么h(n)=0,f(n)=g(n)+h(n)=g(n)+0=f*(n),其中f*(n)为真正的总费用,即f(n)就为最短路径。
(参考:https://bbs.csdn.net/topics/70416815)
其中当h(n)越靠近h*(n),效率越优秀。这一点可以感性理解,越靠近真实值就越准确(证明可以买专业书看
所以A*算法的运行效率取决于估价函数的优劣。
同样的,A*也可以用于求k短路问题。
因为高效率,我们可以一直跑,直到第k次到达终点,即为k短路。
当然,我们既然能够跑多次,那么跑一次最短路绰绰有余。我们不如从终点往回用dijkstra等算法得到终点到每个点的距离,即每个点的h*(n),此时估价函数与实际距离相同,必然最优。
2. IDA*
这是另外一种方向,但仍要用到上面所说的f(n),g(n),和h(n)。
我们不再直接得到费用,而是采取贪心的方法预设一个总费用,并验证是否可行,不可行就增大这个费用,重复操作,直到找到一个可行的费用为止,该费用就是答案(十分暴力
有了这个总费用,相当于给你的算法加了一个天花板,限制了天马行空的枚举。
用代码的形式写出来就是:
if(g(n)+h(n)>f(n)) return;
但别小看这一句,它能大大优化爆搜,使复杂度变得玄学而通过。
h(n)一般每个状态都计算一遍或者从上一个状态转移。
常用于数据量在两位数以内的爆搜题。
技巧:
在求k短路时,可以设置一个数组限制每个点只能到达k次,因为已经是到点x的k短路了,再从x走到终点必然最优也是k短路,所以每个点到达k次以上没有意义,可以剪枝。
代码:
1. A* 洛谷 P4467 [SCOI2007]k短路
没有办法,出题人又卡A*。。。
#include<bits/stdc++.h>
using namespace std;
int e1[10010],w1[10010],nxt1[10010],head1[110],cnt1;
int e2[10010],w2[10010],nxt2[10010],head2[110],cnt2;
int dis[110],n,m,k,a,b;
bool vis[110];
struct data1{
int x,s;
bool operator <(const data1&v)const{
return s>v.s;
}
};
priority_queue<data1>q1;
struct data2{
int x,s;
long long sta;
vector<int>v;
bool operator <(const data2&y)const{
if(s+dis[x]!=y.s+dis[y.x])
return s+dis[x]>y.s+dis[y.x];
for(int i=0;i<min(v.size(),y.v.size());i++)
if(v[i]!=y.v[i]) return</