神奇搜索算法A*
A*
A*是一种启发式搜索算法,又叫最佳图搜索算法。
何谓启发式搜索?
众所周知,计算机在执行搜索算法时是没开上帝视角的。因此,在搜索时,往往显得盲目,把所有可能的状态全部遍历,这种搜索我们统称盲目搜索。
启发式搜索,顾名思义,其实就是给了盲目的计算机一点启发,一点智慧,利用这些来引导计算机减少搜索范围,大幅降低搜索复杂度。试想在BFS的过程中,在某一阶段某些状态成为正解的概率明显比别的状态要大,那我们就优先选择这些状态继续BFS,达到降低复杂度的目的。那么怎么计算这个概率呢?
我们需要给计算机一些启发信息。不同的启发信息有不同的强度,既不能太强也不能弱,太强会导致得不到正解,而太弱又会导致优化效果不明显。都说了这么多了,那就来点具体的。
如何进行启发式搜索?
在A*之前,首先要提一下A算法,A*是A的一种特殊情形。
这里我们要引入一个函数——“估价函数”。
定义一个函数\(f(n)=g(n)+h(n)\),利用启发信息计算出\(h\),根据\(f\)函数的值找出当前搜索状态下的最有希望的节点进行扩展。其中\(n\)是一个状态,\(f\)就是估价函数,\(g\)是\(n\)已经用掉的开销(可以理解做裸的BFS用到的信息),\(h\)是一个启发函数。
启发函数在不同的情况下是有不同的计算方法的,这要因情况而异。
为了更好定义A*算法,我们还要引入一些函数:\(f^ *(n),g^ *(n),h^ *(n)\)。
首先我们要明确,上面提到的估价函数,是对待扩展节点的一个“估计”,是从启发信息计算而来的,因此从初始状态到正解对应的状态的花费不是实际花费。
\(f^ *(n)=g^ *(n)+h^ *(n)\)表示的是从搜索的起点经过\(n\)状态到达搜索目标的实际最小花费,因此,你可以把\(f(n),g(n),h(n)\)看作这些带*的函数的估计值。
A*算法定义为保证\(h(n)<h^ *(n)\)的A算法,其保证能得到最优解。
我们通过例题入手
P1379 八数码难题
题目描述
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
显然这题可以用双向BFS做(起点状态和中点状态模式相同),而且实际上裸的BFS这题也能过。。。
不过既然是在讲A*,就讨论A*怎么解(哇,发现题解有一篇我校巨佬的IDA*QWQ)。
为了使得\(h\)函数一定比\(h^ *(n)\)小,我们考虑构造一个这样的函数。在起始状态,\(h ^*(n)\)显然就是所有位置的数移动到目标状态的位置的所需步数。我们可以假定\(h(n)\)为\(n\)状态时不在目标位置的数的个数。因为显然,这些不再目标位置的数至少要通过一次移动才能到达目标位置。这是一个可行方案。
贴代码:
用了一个map来去除重复状态,其实matrix结构体里面随便咋重载都行,反正装的进去map就行。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
using namespace std;
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
struct matrix{
int a[5][5];
bool operator<(matrix x) const{
for(int i=1;i<=3;++i)
for(int j=1;j<=3;++j)
if(x.a[i][j]!=a[i][j]) return x.a[i][j]<a[i][j];
return 0;
}
}st,f;
inline int h(matrix a)
{
int tmp=0;
for(int i=1;i<=3;++i)
for(int j=1;j<=3;++j)
if(a.a[i][j]!=st.a[i][j]) tmp++;
return tmp;
}
struct node{
matrix a;
int step;
bool operator<(node x) const{
return x.step+h(x.a)<step+h(a);
}
node(){};
node(matrix _a,int _step){a=_a,step=_step;}
};
priority_queue<node> q;
map<matrix,bool> mp;
int main()
{
st.a[1][1]=1,st.a[1][2]=2,st.a[1][3]=3;
st.a[2][1]=8,st.a[2][2]=0,st.a[2][3]=4;
st.a[3][1]=7,st.a[3][2]=6,st.a[3][3]=5;
char c;
for(int i=1;i<=3;++i)
for(int j=1;j<=3;++j){
scanf("%c",&c);
f.a[i][j]=c-'0';
}
q.push(node(f,0));
while(q.size()){
node x=q.top();q.pop();
if(!h(x.a)){
printf("%d\n",x.step);
return 0;
}
if(mp[x.a]) continue;
mp[x.a]=1;
int nx,ny;
for(int i=1;i<=3;++i)
for(int j=1;j<=3;++j)
if(!x.a.a[i][j]) nx=i,ny=j;
for(int i=0;i<4;++i){
int xx=nx+dir[i][0],yy=ny+dir[i][1];
if(xx>=1&&xx<=3&&yy>=1&&yy<=3){
swap(x.a.a[xx][yy],x.a.a[nx][ny]);
q.push(node(x.a,x.step+1));
swap(x.a.a[xx][yy],x.a.a[nx][ny]);
}
}
}
return 0;
}
如果要加ID的话会更快,这里不详细讲,具体做法跟一般IDBFS是一样的。
P2324 [SCOI2005]骑士精神
跟上面这道题完全一样,没区别,稍微改一下代码就好了。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#define ll long long
using namespace std;
int dir[8][2]={{1,2},{-1,2},{2,1},{2,-1},{-2,1},{-2,-1},{1,-2},{-1,-2}};
struct matrix{
int a[6][6];
bool operator<(matrix x) const{
for(int i=1;i<=5;++i)
for(int j=1;j<=5;++j)
if(x.a[i][j]!=a[i][j]) return x.a[i][j]<a[i][j];
return 0;
}
}st,f;
inline int h(matrix a)
{
int tmp=0;
for(int i=1;i<=5;++i)
for(int j=1;j<=5;++j)
if(a.a[i][j]!=st.a[i][j]) tmp++;
return tmp;
}
struct node{
matrix a;
int step;
bool operator<(node x) const{
return x.step+h(x.a)<step+h(a);
}
node(){};
node(matrix _a,int _step){a=_a,step=_step;}
};
priority_queue<node> q;
map<matrix,bool> mp;
int main()
{
st.a[1][1]=1,st.a[1][2]=1,st.a[1][3]=1,st.a[1][4]=1,st.a[1][5]=1;
st.a[2][1]=0,st.a[2][2]=1,st.a[2][3]=1,st.a[2][4]=1,st.a[2][5]=1;
st.a[3][1]=0,st.a[3][2]=0,st.a[3][3]=-1,st.a[3][4]=1,st.a[3][5]=1;
st.a[4][1]=0,st.a[4][2]=0,st.a[4][3]=0,st.a[4][4]=0,st.a[4][5]=1;
st.a[5][1]=0,st.a[5][2]=0,st.a[5][3]=0,st.a[5][4]=0,st.a[5][5]=0;
int T;
scanf("%d",&T);
while(T--){
mp.clear();
while(q.size()) q.pop();
char c;
for(int i=1;i<=5;++i)
for(int j=1;j<=5;++j){
scanf(" %c",&c);
if(c=='*') f.a[i][j]=-1;
else f.a[i][j]=c-'0';
}
bool flag=0;
q.push(node(f,0));
while(q.size()){
node x=q.top();q.pop();
if(flag) continue;
if(x.step>15){
printf("-1\n");
flag=1;
}
if(!h(x.a)&&!flag){
printf("%d\n",x.step);
flag=1;
}
if(mp[x.a]) continue;
mp[x.a]=1;
int nx,ny;
for(int i=1;i<=5;++i)
for(int j=1;j<=5;++j)
if(x.a.a[i][j]==-1) nx=i,ny=j;
for(int i=0;i<8;++i){
int xx=nx+dir[i][0],yy=ny+dir[i][1];
if(xx>=1&&xx<=5&&yy>=1&&yy<=5){
swap(x.a.a[xx][yy],x.a.a[nx][ny]);
q.push(node(x.a,x.step+1));
swap(x.a.a[xx][yy],x.a.a[nx][ny]);
}
}
}
}
return 0;
}
就是跑的比较慢啦。