(洛谷 R6765172)八数码问题 DBFS+hash 乱搞

非常经典的一道搜索练手题,各种方法花式求解(虽然目前只搞定了 DBFS+hash,但是可以说说想法XD)
题目描述:

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。(emm没错我就是扒过来的

状态之间的转移非常简单,很朴素的一个想法就是开个 3x3 的数组,然后一步一步的交换;
不过这样写太累了,加上楼主又懒。。于是果断不写这个(其实可以写,acer在IDA里用这个实现了,一会儿友链)
其实题目还给了一个 样例输入 还有 目标状态 ,长这样:283104765  或者  283104765
这给了我们一个很好的提示,3x3 判重+更新太累了 T^T ,那不如用一个 1x9的数组判重+更新;
再进一步想,既然我 1x9 ,那为什么不直接 压缩成一个数呢 : )

那问题的状态转移的问题就解决了,数字入队,数组出队,更改,压数字,入队,and go on and on and on

-----------------分割线---------------------------------------------------------------------------------------


基本思路就是这样,讲一下思路代码框架 : 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)
dis更新的时候,下标可以和hash的下标对应,hash的时候,取余的质数要> 9 ! (<-这是阶乘
如果你们哪个大神会康拓展开,可以做到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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值