非常经典的一道搜索练手题,各种方法花式求解(虽然目前只搞定了 DBFS+hash,但是可以说说想法XD)
题目描述:
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。(emm没错我就是扒过来的
状态之间的转移非常简单,很朴素的一个想法就是开个 3x3 的数组,然后一步一步的交换;
不过这样写太累了,加上楼主又懒。。于是果断不写这个(其实可以写,acer在IDA里用这个实现了,一会儿友链)
其实题目还给了一个 样例输入 还有 目标状态 ,长这样:283104765 或者 283104765
其实题目还给了一个 样例输入 还有 目标状态 ,长这样:283104765 或者 283104765
这给了我们一个很好的提示,3x3 判重+更新太累了 T^T ,那不如用一个 1x9的数组判重+更新;
再进一步想,既然我 1x9 ,那为什么不直接 压缩成一个数呢 : )
再进一步想,既然我 1x9 ,那为什么不直接 压缩成一个数呢 : )
-----------------分割线---------------------------------------------------------------------------------------
基本思路就是这样,讲一下思路代码框架 : DBFS + hash+乱搞;
DBFS : BFS我们都会写,但是BFS在某些情况下会出问题。比如,当搜索树扩展到后期的时候,每更新一次状态,就会扩展出几个甚至更坏的时候能扩展至10+(当然不是这道题),如果你用的是假队列的话,那下标就会被更新到很大,有炸数组的风险,
如果更惨的话,用真队列,直接MLE,而且BFS扩展的虽然快,但是会发现大多数时候,扩展的都是些没用的节点,而且在错误路径上继续扩展下去,造成空间上的浪费;
所以我们就想尽可能的剪掉不必要的树枝,可以发现,这道题的搜索树是这样的:
发现,这棵树是*2,*4,*2,*4增长的,越往后空间浪费越大,而且题目给出了明显的结束状态,所有可以用双向搜索,当源点与汇点的路径有重叠时,即证明找到路径,然后这棵树可能长这样:
(意会。。。)
总之,巨幅减少扩展节点;
所以给出DBFS大体框架:
DBFS(int s ,int end){
queue<int >q1;queue<int >q2;
q1.push(s);q2.push(end);
给出初始状态;
while(q1.size()&&q2.size()){//优先更新队列容量小的,因为要保持速度一致(相等时效率最快)
if(q1.size()<q2.size()){扩展q1;(包括判断,入队,更新);if(扩展出在q2 扩展过的节点)得解+return 1;}
else {
扩展q2;(包括判断,入队,更新)if(扩展出在q1 扩展过的节点)得解+return 1;}
}
while(q1不为空)
{扩展q1;(包括判断,入队,更新);if(扩展出在q2 扩展过的节点)得解+return 1;
while(q2不为空)
{扩展q2;(包括判断,入队,更新);if(扩展出在q1 扩展过的节点)得解+return 1;
return 1;
}
}
本题注意事项:如果要压数字的话,记得处理 0 在,末尾或 开头(尤其是0在头部(我懒,就把 123456780 -> 234567891)
swap更新的时候,下标要和自己的数组对上,,,(我傻,,,正好错开一位,,竟然过样例了XD)
swap更新的时候,下标要和自己的数组对上,,,(我傻,,,正好错开一位,,竟然过样例了XD)
dis更新的时候,下标可以和hash的下标对应,hash的时候,取余的质数要> 9 ! (<-这是阶乘
如果你们哪个大神会康拓展开,可以做到hash不挂表;
如果你们哪个大神会康拓展开,可以做到hash不挂表;
emm文末贴acer友链(我知道你们都是去看他的IDA的 ((¬︿̫̿¬☆))山 )(我的比他的快(傲娇脸))
贴代码(一点点注释)
#include<iostream>
#include<cstring>
#include<queue>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
#define M 883783//比P大
#define P 383783//P 要大于 9得阶乘 ,因为。。数学
int hash[M][3],a[10],nxt[M][3],xx;
int dis[M][3];
int dir[9][5]={
{-1,-1,3,1},{-1,0,4,2},{-1,1,5,-1},
{0,-1,6,4},{1,3,7,5},{2,4,8,-1},
{3,-1,-1,7},{4,6,-1,8},{5,7,-1,-1}
}; //dir[当前0的位置][可以swap到哪里去]//-1为不能动,记得判
int insert(int x,int num){ //自己YY 的hash简陋的不行不行的 //记得分开插入
int flag=0;int cnt=x%P+1;
while(hash[cnt][num]!=-1){
flag=1;cnt++;if(cnt>=M)return 0;
}
if(flag)nxt[x%P][num]=cnt;
hash[cnt][num]=x;
return 1;
}
int get(int x,int num){ //查询是否存在,以及若存在,其下标为何 //分开查找
int sub=x%P+1;
if(hash[sub][num]==x)return sub;
while(hash[nxt[sub][num]][num]!=x){
if((nxt[sub][num]==-1)||(hash[nxt[sub][num]][num]==-1))return 0;
sub=nxt[sub][num];
}return sub;
}
int pullout(int x[10]){int num=x[0];for(int i=1;i<9;i++){num=num*10;num+=x[i];}return num;}//自己YY的从数组压成数
int pullin(int num){for(int i=8;i>=0;i--){a[i]=num%10;num/=10;}return 0;}//YY的数组搞回数
queue<int>q;queue<int>q2;// <-队列在这
int expand(int num){//扩展函数,兼带判重 入队 得解,
int c[10],sub,now;
if(num==1){
now=q.front();q.pop();
}else {
now=q2.front();q2.pop();
}
pullin(now);
for(int i=0;i<9;i++){if(a[i]==1){sub=i;break;}}
for(int i=0;i<4;i++){
if(dir[sub][i]==-1)continue;
memcpy(c,a,sizeof(c));//一个扩展成四个(二个)
swap(c[sub],c[dir[sub][i]]);
int xx=pullout(c);//printf("xx=%d\n",xx);
if(!get(xx,num)){
insert(xx,num);dis[get(xx,num)][num]=dis[get(now,num)][num]+1;
if(get(xx,3-num)){//查另一个队列是否有值
printf("%d\n",dis[get(xx,num)][num]+dis[get(xx,3-num)][3-num]);
return 1;
}
if(num==1){q.push(xx);}else {q2.push(xx);}
}
}return 0;
}
int BFSdouble(int s,int end){//DBFS框架
q.push(s);q2.push(end);
insert(s,1);insert(end,2);
dis[get(s,1)][1]=0;dis[get(end,2)][2]=0;
while(q.size()&&q2.size()){
if(q.size()<q2.size()){
if(expand(1))return 1;
}else{
if(expand(2))return 1;
}
}
while(q.size())if(expand(1))return 1;
while(q2.size())if(expand(2))return 1;
return 0;
}
int main(){
memset(hash,-1,sizeof(hash));
memset(nxt,-1,sizeof(nxt));
scanf("%d",&xx);
if(xx==123804765){//如果是目标直接得 0 ,如果去搜会在一个队里先扩展一次,才能扩展回去 为2步,其余情况不会有问题
printf("0\n");return 0;
}
pullin(xx);
for(int i=0;i<9;i++)a[i]+=1;//处理0
xx=pullout(a);
int yy=234915876;//处理0
BFSdouble(xx,yy);
return 0;
}
顺带发一下我的hash(主要在思想),网上的很麻烦的样子,,(一堆指针写的)
#include<iostream>
#include<cstring>
#include<queue>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
#define M 683783
#define P 20
int hash[M],nxt[M],xx;
int insert(int x){
int flag=0;int cnt=x%P+1;
while(hash[cnt]!=-1){
flag=1;cnt++;
}
if(flag)nxt[x%P]=cnt;
hash[cnt]=x;printf("numbernow's cnt=%d\n",cnt);
return 1;
}
int get(int x){
int sub=x%P+1;
if(hash[sub]==x){return sub;}
while(hash[sub]!=x){
if((nxt[sub]==-1)||(hash[nxt[sub]]==-1))return 0;
sub=nxt[sub];
}return sub;
}
int main(){
memset(hash,-1,sizeof(hash));
memset(nxt,-1,sizeof(nxt));
scanf("%d",&xx);insert(xx);//输入一个数
while(1){//查询某个数
scanf("%d",&xx);
int ans=get(xx);
if(ans)printf("sub for number=%d\n",ans);
else puts("Nope!");
}
}
友链:
go