骑士精神笔记(IDA*)
万恶之源
做法
这道题就是一道很典型的搜索题,可以像滑雪那样用深搜一路枚举到最后复原,也可以像马的遍历那样用广搜逐层扩展的一步步的拓展。但是,这道题的深度可能非常深,需要枚举的情况又非常多,状态也很难保存(当然是可以保存的),单纯的深搜和广搜都很难完成这题,因此迭代加深诞生了!!
迭代加深简单来说就是逐层拓展的深搜,显然它既有广搜也有深搜的特性,具体性质这里不赘述。
我们可以将移动步数作为层数,一层层加深,在加深的过程中枚举可能的移动方式,这样一直到十五层一定可以找出答案。
但是这样也并不能过这道题,因为一层层下去可能的情况是指数级增长的,因此我们还需要继续优化。。。可以选择双向bfs,A * 和IDA*。
我莫名其妙用了IDA*参考题解的,感觉好像还是能够接受?
核心代码
void IDA(int dep,int x,int y,int maxdep) {
if (dep == maxdep) {
if (!hx()) suc = 1;
return;
}
for (register int j = 1; j <= 8; j++) {
int xx = x + dx[j];
int yy = y + dy[j];
if (!check(xx,yy)) continue;
swap(G[x][y],G[xx][yy]);
int fx = dep + hx();//评价函数
if (fx <= maxdep) { //估计未来的深度是否会超过最大深度
IDA(dep+1,xx,yy,maxdep);
}
swap(G[x][y],G[xx][yy]);//回溯
}
}
ID和普通的DFS主要就是添加了逐层拓展这一特性,这一特性在main函数中用一个递增IDA的maxdep参数的循环体现
for (register int Maxdep = 1; Maxdep <= 15; Maxdep++) {
IDA(0,wx,wy,Maxdep);
if (suc == 1) {printf("%d\n",Maxdep);break;}
}
但是我们这里是IDA,它与ID的主要差别就是增加了启发函数*,如同A*算法是比BFS多了个启发函数。
这个优化简直如虎添翼
定义:
f(n)=g(n)+h(n)
其中n是一个状态,f就是评价函数,g是n已经用掉的开销,h是一个启发函数。
当h(n)=0时,A算法本质就是普通的堆优化bfs
h(n)是启发式搜索的核心,因为g(n)就相当于我们平时的一些堆优化之类的,h
简单来说h(n)就是对未来有个估计,看有多少希望达到目标,上面的IDA* 就将有多少棋子不在自己该在的位置上作为h(n)函数,虽然看着没什么,但是却能带来非常非常非常神奇的优化。
此题糟点(细节问题)
- 首先学习一种看上去好像挺厉害的逐个读取字符的方式,读取字符这块搞到我都头疼,一开始本来用cin,但是它好像很慢,所以就改了,但是并没有加快多少,但还是学习学习趴
void readChar(char &c) {
for (c = getchar(); c != '0' && c != '1' && c != '*'; c = getchar());
}
2. 启发函数的实际应用
inline int hx() { //启发函数,评估当前状态有多少个点不在自己的原位上,除了空白
int cnt = 0;
for (register int i = 1; i <= 5; i++) {
for (register int k = 1; k <= 5; k++) {
if (G[i][k]!=goal[i][k]) {//&& G[i][k]!=2
cnt++;
}
}
}
return cnt;
}
if (dep + hx <= maxdep) {
IDA(dep+1,xx,yy,maxdep);
}
直观来讲就是估计还有多少步才能到目标,估计之后在范围之内才继续搜索,因为估计值一定小于实际值,若是估计值都大于范围了,那实际搜索肯定也会大于它,所以没必要搜下去。
-
网格题一定都要记得带上判断是否超界的函数!!!
-
起点应该是空白格
-
这里的搜索函数不是bool型的,因为我们利用的是maxdep来确定答案(当然我感觉应该可以改成bool型的)
-
特殊情况要特判,特判一开始就正确的图
-
其实这题就算用了IDA* 也还是过不了,需要一点点常数优化。
为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数
register是将一些频繁用到的变量放在cpu的寄存器以提高效率
加上这两个小小的优化就A啦( •̀ ω •́ )y
当然你还得学习更多更有效的优化!!!