学习笔记(2)启发式搜索
启发式搜索当中的 \(A*\),\(IDA*\),在传统的搜索算法基础上引入估价函数 \(h[x]\) 对当前结点/状态进行估价以更好地扩展路径,可以近似看作:
\(优先队列BFS/Dijkstra+h[x]\) -> \(A*\)
\(DFS\)(迭代加深)\(+h[x]\) -> \(IDA*\)
每个结点的最终“性价比” \(=\) 权值 \(+\) 估价
· A*
一个好的估价函数的选择会影响程序的最终效率。任意时刻下估价函数的值小于当前结点到目标结点的实际距离,否则可能使算法失效而无法得出“最优路径”。好的估价函数会在不断搜索中逐渐变得“精确”(搜索到后面时\(h[x]\)的影响逐渐变大,最终占据主导,若该函数值越接近真实值,计算出的价值也越接近实际),越精确程序效率越高。
在\(bfs/dijkstra\)的过程中,计算每个点的\(g[x]+h[x]\)并加入优先队列中,每次选取价值最高的点扩充路径并记录父节点(便于记录路径)即可
· IDA*
又称迭代加深的\(A*\)
对到达目标点的步数进行限制,若总花费在限定步数以内则继续\(dfs\),适用于答案在已知/较浅层的时候(可加记忆化进行优化)
· 例题
\(P1379\) 八数码难题
A*做法
#include <bits/stdc++.h>
using namespace std;
int ans,x,y;
int x_c[4]={-1,1,0,0};
int y_c[4]={0,0,-1,1};
int gol[4][4]={
{0,0,0,0},
{0,1,2,3},
{0,8,0,4},
{0,7,6,5}
};
struct sit{
int f[4][4];
bool operator < (const sit &b)const{
for(int i=1;i<=3;i++){
for(int j=1;j<=3;j++){
if(f[i][j]!=b.f[i][j]) return f[i][j]<b.f[i][j];
}
}
return 0;
}
};
int h(sit k){
int res=0;
for(int i=1;i<=3;i++){
for(int j=1;j<=3;j++){
if(k.f[i][j]!=gol[i][j]) ++res;
}
}
return res;
}
struct node{
int d;
sit fl;
bool operator < (const node &b)const{
return d+h(this->fl) > b.d+h(b.fl);
}
}a;
priority_queue<node> q;
set<sit> s;
void bfs(){
q.push(a);
while(q.size()){
node tmp=q.top(); q.pop();
if(!h(tmp.fl)){
printf("%d",tmp.d);
exit(0);
}
for(int i=1;i<=3;i++){
for(int j=1;j<=3;j++){
if(tmp.fl.f[i][j]==0) x=i, y=j;
}
}
for(int i=0;i<4;i++){
int xt=x+x_c[i];
int yt=y+y_c[i];
if(xt<1||xt>3||yt<1||yt>3) continue;
swap(tmp.fl.f[x][y],tmp.fl.f[xt][yt]);
if(!s.count(tmp.fl)){
s.insert(tmp.fl);
q.push(node{tmp.d+1,tmp.fl});
}
swap(tmp.fl.f[x][y],tmp.fl.f[xt][yt]);
}
}
}
int main(){
for(int i=1;i<=3;i++){
for(int j=1;j<=3;j++){
char ch;
scanf("%c",&ch);
a.fl.f[i][j]=ch-'0';
}
}
a.d=0;
bfs();
return 0;
}
\(P2324\) 骑士精神
选取当前状态与目标状态的差距作为估价函数(由于限定步数,IDA*会稍微好写一点)
IDA*做法
#include <bits/stdc++.h>
#define N 10
using namespace std;
int t,ans,st_x,st_y;
int a[N][N];
bool success;
int x_c[8]={-2,-1,1,2,-2,-1,1,2};
int y_c[8]={-1,-2,-2,-1,1,2,2,1};
int gol[N][N]={
{0,0,0,0,0,0},
{0,1,1,1,1,1},
{0,0,1,1,1,1},
{0,0,0,2,1,1},
{0,0,0,0,0,1},
{0,0,0,0,0,0},
};
int h(){
int res=0;
for(int i=1;i<=5;i++){
for(int j=1;j<=5;j++){
if(a[i][j]!=gol[i][j]) ++res;
}
}
return res;
}
void dfs(int dep,int x,int y,int maxdep){
if(dep==maxdep){
if(!h()) success=1;
return;
}
for(int i=0;i<8;i++){
int xt=x+x_c[i];
int yt=y+y_c[i];
if(xt<1||xt>5||yt<1||yt>5) continue;
swap(a[x][y],a[xt][yt]);
if(dep+h()<=maxdep) dfs(dep+1,xt,yt,maxdep);
swap(a[x][y],a[xt][yt]);
}
}
int main(){
scanf("%d",&t);
while(t--){
for(int i=1;i<=5;i++){
for(int j=1;j<=5;j++){
char ch;
scanf(" %c",&ch);
if(ch=='*') a[i][j]=2,st_x=i,st_y=j;
else a[i][j]=ch-'0';
}
}
ans=-1;
success=0;
for(int i=1;i<=15;i++){
dfs(0,st_x,st_y,i);
if(success){
ans=i;
break;
}
}
printf("%d\n",ans);
}
return 0;
}
所以总而言之其实主要的难点就是估价函数、