POJ1077八数码问题~~bfs

八数码问题 最简单的解法:

康托展开+bfs   

(只会这一种)

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

首先这个题目告诉了我们 9位的数字,这就是状态,我们需要把这个状态存起来,那么怎么应该存呢?

我们使用一个9位的数组,来表示当前的实际状态(注意一维和二维的转换)

那么虚拟状态呢?就是判重的根据,因为bfs本身就很暴力,而且这道题的数据量又是贼**大,你让他炒鸡暴力的话肯定会被T掉,那么怎么设置vis[ ]数组用来标记呢?

开一个大小为987654321的数组吗?显然不可能,内存早就爆掉了

很明显  状态中不可能出现 两个一样的数字,也就是说,所有的状态一定是0-8这九个数字的全排列,其实就只有9!=362880种状态,数据量一下就减小了接近3000倍吧,内存应该是可以存的下的;接下来就是这道题的网红了====康托展开式

那么什么是康托展开?↓↓↓

https://baike.baidu.com/item/%E5%BA%B7%E6%89%98%E5%B1%95%E5%BC%80/7968428?fr=aladdin

//康拓展开式 
const int jie[]={1,1,2,6,24,120,720,5040,40320,362880};//0-9的阶乘  
int cantor(char a[]){
	int n=9;
	int res=0;
	for(int i=0;i<n-1;i++){ 
		int sum=0;
		for(int j=i+1;j<n;j++)
			if(a[j]<a[i])
				sum++;//找到后面比当前数小的数  
		res+=sum*jie[a[i]-1];
	}
	return res;
} 

返回的是某一种组合在这些数字的全排列 从小到大 顺序中的位置,就是前面有几个比他小的,我们就使用它的位置来储存这个状态,谁能想的到呢?还是听老师讲的

后面就是bfs的过程了,我自己也是个需要勤加练习的娃啊

再一个就是比较一些数组函数的运用 memcpy和memcmp,以前确实没见过,跟着这道题,长了见识了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
using namespace std;
const int inf=0x3f3f3f3f;
const int mm=362888;//9的阶乘种状态 

int vis[mm];
struct node{
	int ss[9];
	int step;
}; 

int sta[9],goal[9];//起始和目标状态  
int to[4][2]={0,1,1,0,0,-1,-1,0};

//康拓展开式 
const int jie[]={1,1,2,6,24,120,720,5040,40320,362880};//0-9的阶乘  
int cantor(int a[],int n){
	int res=0;
	for(int i=0;i<n-1;i++){ 
		int sum=0;
		for(int j=i+1;j<n;j++)
			if(a[j]<a[i])
				sum++;//找到后面比当前数小的数  
		res+=sum*jie[n-i-1];
	}
	if(vis[res]==0){
		vis[res]=1;
		return 1;
	}
	return 0;
} 

int bfs(){
	node fa;
	memcpy(fa.ss,sta,sizeof(fa.ss));//数组复制函数,复制起点状态  
	fa.step=0; 
	queue<node>q;
	cantor(fa.ss,9);//给vis赋初值  
	q.push(fa);
	while(!q.empty()){
		fa=q.front();
		q.pop();
		if(memcmp(fa.ss,goal,sizeof(goal))==0)
			return fa.step;
		
		int loc;//找到 0的位置 
		for(loc=0;loc<9;loc++)
			if(fa.ss[loc]==0)
				break;
		int x=loc%3;//转换成二维  
		int y=loc/3;
		for(int i=0;i<4;i++){//枚举方向
			 int nextx=x+to[i][0];
			 int nexty=y+to[i][1];
			 if(nextx>=0&&nextx<3&&nexty>=0&&nexty<3){
			 	int nextloc=nextx+nexty*3;//换成一维  
			 	//必须生成副本 不然会影响下一次循环  
			 	node nextnode=fa;//原状态转移  
				swap(nextnode.ss[nextloc],nextnode.ss[loc]);// 
			 	nextnode.step++;
			 	if(memcmp(nextnode.ss,goal,sizeof(goal))==0)
			 		return nextnode.step;
			 	if(cantor(nextnode.ss,9))
			 		q.push(nextnode);
			 }
		}
	}
	return -1;
}

int main()
{
	for(int i=0;i<9;i++)
		cin>>sta[i];
	for(int i=0;i<9;i++)
		cin>>goal[i];
	int res=bfs(); 
	if(res==-1)printf("no!\n");
	else cout<<res<<endl;
	return 0;
}

POJ-1077

同样是一个八数码问题,题目稍作修改,增加了一个路径输出的过程

这是个很奇妙的问题,模拟一个来储存路径,这道题因为输入错误搞了一晚上,,,脑子瓦特了

HDU-1034是个同名同义的题目,只不过数据更加严格,导致下面的做法弄不出来,还有更深奥的做法等着探索啊

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
using namespace std;
const int inf=0x3f3f3f3f;
const int mm=362888;//9的阶乘种状态 

char step[mm];
int pre[mm];
int vis[mm];

struct node{
	int ss[9];//9位表示 真实状态  
//	int step;
}; 

int to[4][2]={0,1,1,0,0,-1,-1,0};//四个方向  

//康拓展开式 
const int jie[]={1,1,2,6,24,120,720,5040,40320,362880};//0-9的阶乘  
int cantor(int a[],int n){
	int res=0;
	for(int i=0;i<n-1;i++){ 
		int sum=0;
		for(int j=i+1;j<n;j++)
			if(a[j]<a[i])
				sum++;//找到后面比当前数小的数  
		res+=sum*jie[n-i-1];
	}
	return res;
} 

void bfs(node fa){
	mem(vis,0);
	queue<node>q;
	int u=cantor(fa.ss,9);//给vis赋初值 
	vis[u]=1; 
	pre[u]=-1;//用来回溯起点   
	q.push(fa);
	
	while(!q.empty()){
		fa=q.front();
		int u=cantor(fa.ss,9);
		q.pop();
		
		int loc;//找到 0的位置  
		for(loc=0;loc<9;loc++)
			if(fa.ss[loc]==9)
				break;
		
		int x=loc/3;//转换成二维  
		int y=loc%3;
		for(int i=0;i<4;i++){//枚举方向
			 int nextx=x+to[i][0];
			 int nexty=y+to[i][1];
			 if(nextx>=0&&nextx<3&&nexty>=0&&nexty<3){
			 	int nextloc=nextx*3+nexty;//换成一维  
			 	//必须生成副本 不然会影响下一次循环  
			 	node nextnode=fa;//原状态转移  
				swap(nextnode.ss[nextloc],nextnode.ss[loc]);
				int v=cantor(nextnode.ss,9);
				if(!vis[v]){
					step[v]=i;//存方向 
					vis[v]=1;
					pre[v]=u;//前一个位置 
					if(v==0)
						return;
					q.push(nextnode);
				}
			 }
		}
	}
}

//路径回溯  难! 
void show(){
	int n,u;
	char path[1000]; 
	n=1;
	path[0]=step[0];
	u=pre[0];
	while(pre[u]!=-1){//往前找节点  
		path[n]=step[u];//模拟栈存路径方向   
		n++;
		u=pre[u];
	}
	for(int i=n-1;i>=0;i--)//和前面匹配起来,表示四个方向  
		if(path[i]==0)
			cout<<'r';
		else if(path[i]==1)
			cout<<'d';
		else if(path[i]==2)
			cout<<'l';
		else cout<<'u';
}

int main()
{
	node sta;
	//不要输入字符串 不然都不知道哪里错的。。。。 
	char ch;
	for(int i=0;i<9;i++){
		cin>>ch;
		if(ch=='x')
			sta.ss[i]=9;
		else sta.ss[i]=ch-'0';// 
	}
	bfs(sta); 
	if(vis[0])
		show();
	else cout<<"unsolvable";
	
	cout<<endl;
	
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章