今天先介绍启发式搜索,也就是A*,后续我会继续推出双向广搜与迭代加深启发式搜索(IDA*)。
启发式搜索
何为启发式搜索?
我们在搜索的时候往往会发现一些数据会导致我们的普通的深搜与广搜都无法通过,那这个时候我们就需要让起点与终点建立一些联系,今天我们讲述的启发式搜索就是在广度优先搜索的基础上加了这样一个优化,叫做估价函数。
估价函数
对于一个状态,在我们知道终点状态的时候,我们可以设计一个估价函数使我们对这个状态距离终点的距离有个简单的了解,并且我们以后就优先取出估价值+目前的步数最小的那个状态进行扩展。
估价函数的实现因题而异,一个好的估价函数会让算法变得很优秀。但是要注意的一点是,估价函数都要是一个比实际情况更优的估值,也就是说我们的估价值要始终 < = <= <=实际步数。
启发式搜索的实现
因为我们每次都拿出步数+估价值最优的状态进行扩展,所以我们要把队列变成,优先队列。这个实现有点像堆优化后的迪杰斯特拉算法。
步骤:
1.将起点初始化之后放入优先队列。
2.在找到答案且优先队列为空之前拿出一个堆顶元素,判断是否为答案。
3.如果是直接结束循环,输出答案。
4.如果不是就先标记然后向外扩展,遇到一个没出过堆的元素就将其估价然后放入堆(注意是没有出过堆,因为一个状态可以入队很多次)
八数码问题
题目链接http://acm.hdu.edu.cn/showproblem.php?pid=1043
康托展开
对于八数码问题,我们会发现如果我们强行记录每一个状态的话就要开一个 1 0 9 10^9 109的数组,这样显然会超内存,所以我们这里要介绍一种叫做康托展开的方式。
康托展开,就是求一个排列在其全排列中的字典序排名。
其公式是:
X = ∑ i = n 1 a ∗ ( i − 1 ) ! X=\sum_{i=n}^{1}a*(i-1)! X=i=n∑1a∗(i−1)! 下标从左到右降序排列,其中a表示该位右边有几个数字比这位数字大。
康托展开代码实现:
struct tree{//树状数组用于计算康托值
int c[10];
void clear1(){
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++){
modify(i,1);
}
}
int lowbit(int x){
return x&(-x);
}
void modify(int pos,int x){
for(int i=pos;i<=n;i+=lowbit(i)){
c[i]+=x;
}
}
int query(int pos){
int ans=0;
for(int i=pos;i>0;i-=lowbit(i)){
ans+=c[i];
}
return ans;
}
}t;
int jc[10];
void work(){
jc[0]=1;
for(int i=1;i<=n;i++){
jc[i]=jc[i-1]*i;
}
return;
}
int cantor(int *in){
t.clear1();
int ans=1;
for(int i=1;i<=n;i++){
int cnt=t.query(in[i]-1);
ans+=cnt*jc[n-i];
t.modify(in[i],-1);
}
return ans;
}
代码实现
最后的ac代码:
#include<iostream>
#include<cstdio>
#include<stack>
#include<queue>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
//#include<bits/stdc++.h>
using namespace std;
string str;
int n=9;
int use[10];
struct tree{//树状数组用于计算康托值
int c[10];
void clear1(){
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++){
modify(i,1);
}
}
int lowbit(int x){
return x&(-x);
}
void modify(int pos,int x){
for(int i=pos;i<=n;i+=lowbit(i)){
c[i]+=x;
}
}
int query(int pos){
int ans=0;
for(int i=pos;i>0;i-=lowbit(i)){
ans+=c[i];
}
return ans;
}
}t;
int jc[10];
void work(){
jc[0]=1;
for(int i=1;i<=n;i++){
jc[i]=jc[i-1]*i;
}
return;
}
int cantor(int *in){
t.clear1();
int ans=1;
for(int i=1;i<=n;i++){
int cnt=t.query(in[i]-1);
ans+=cnt*jc[n-i];
t.modify(in[i],-1);
}
return ans;
}
int first_cantor;//起始状态
stack<int>ans;//记录答案的栈,便于逆序输出
struct node{
int map[10];//九宫格状态
int cantor;
int val;//当前的步数+估价
int come;//上一步是怎么过来的 1234对应左右上下
int fa;//上一个状态
int dist;//步数
}p[400000];
int x[10]={0,1,2,3,1,2,3,1,2,3};
int y[10]={0,1,1,1,2,2,2,3,3,3};
int xx[4]={-1,1,3,-3};//广搜状态所用
int yy[4]={1,2,3,4};
int judgex[4]={-1,1,0,0};//71 72都是坐标改变
int judgey[4]={0,0,1,-1};
bool vis[400000];
int guess(node d){//估价
int ans=0;
for(int i=1;i<=n;i++){
if(d.map[i]!=9){
ans+=abs(x[i]-x[d.map[i]])+abs(y[i]-y[d.map[i]]);
}
}
//cout<<endl;
return ans;
}
bool operator<(node a,node b){
return a.val>b.val;
}
priority_queue<node>q;
bool flag=0;//是否找到答案
int dist[400000];//存储每个康托值所对应的最优答案
void bfs(){
node k=q.top();
vis[k.cantor]=1;
q.pop();
if(k.cantor==1){
flag=1;
while(k.fa!=-1&&k.fa!=0){
ans.push(k.come);
k=p[k.fa];
}
return;
}
if(dist[k.cantor]!=k.dist)return;
int id_x=0;//9所在的地方
int x1=0,y1=0; //对应的坐标
node b;//待扩展的新节点
//cout<<k.cantor<<endl;
for(int i=1;i<=n;i++){
//cout<<k.map[i]<<" ";
if(k.map[i]==9){
id_x=i;
x1=x[id_x];
y1=y[id_x];//找到9
}
b.map[i]=k.map[i];
}
//cout<<endl;
b.fa=k.cantor;
for(int i=0;i<4;i++){//4方向扩展
if(x1+judgex[i]>3||x1+judgex[i]<1)continue;
if(y1+judgey[i]>3||y1+judgey[i]<1)continue;
swap(b.map[id_x],b.map[id_x+xx[i]]);
b.cantor=cantor(b.map);
if(dist[k.cantor]+1>=dist[b.cantor]||b.cantor==k.fa||vis[b.cantor]){
swap(b.map[id_x],b.map[id_x+xx[i]]);
continue;
}
b.dist=k.dist+1;
dist[b.cantor]=dist[k.cantor]+1;
b.come=yy[i];
b.val=b.dist+guess(b);
//cout<<guess(b)<<endl;
p[b.cantor]=b;
q.push(b);
swap(b.map[id_x],b.map[id_x+xx[i]]);
}
return;
}
int main(){
work();
while(getline(cin,str)){
memset(dist,0x3f3f3f3f,sizeof(dist));
memset(vis,0,sizeof(vis));
flag=0;
while(!q.empty())q.pop();
int tmp=0;
for(int i=0;i<str.size();i++){
if(str[i]==' ')continue;
if(str[i]=='x')use[++tmp]=9;
else use[++tmp]=str[i]-'0';
}
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(use[j]>use[i]&&use[j]!=9&&use[i]!=9){
// swap(used[j],used[j+1]);
cnt++;
}
}
}
if(cnt%2==1){
printf("unsolvable\n");
continue;
}
first_cantor=cantor(use);
p[first_cantor].dist=0;
p[first_cantor].cantor=first_cantor;
p[first_cantor].fa=-1;
p[first_cantor].come=-1;
p[first_cantor].val=guess(p[first_cantor]);
for(int i=1;i<=n;i++){
p[first_cantor].map[i]=use[i];
}
q.push(p[first_cantor]);
dist[first_cantor]=0;
while(!q.empty()&&!flag){
bfs();
}
while(!ans.empty()){
int x=ans.top();
if(x==1){
printf("l");
}
else if(x==2){
printf("r");
}
else if(x==3){
printf("d");
}
else {
printf("u");
}
ans.pop();
}
printf("\n");
}
return 0;//4 7 2 3 8 6 1 x 5
//6 x 3 7 1 2 4 5 8
//5 7 4 1 8 x 2 3 6
}