【广搜经典】八数码问题 BFS+编码判重

版权声明: https://blog.csdn.net/zryabc/article/details/79648587

八数码问题是人工智能领域的经典问题,自计算机大规模地走入了人们的生活后,研究它的文章,算法数不胜数。常见的有BFS,A*算法等。很多像作者一样的萌新,在面对着其他的大牛们的博客滔滔不绝的谈论各种高深的算法的时候,往往感到了接受乏力。我当初看到这道题的时候,也去看了大神们的博客,往往一知半解。于是,便想写一篇通俗易懂的八数码问题的题解。

题目描述

八数码问题,就是在一个含有1-8和x的3*3方格中,每次可以将x与其相邻位置的数字交换。使得最后变成

1 2 3
4 5 6
7 8 x

你要做的就是实现八数码的解决方案,并要求交换次数最少。

输入

输入一个3*3的矩阵,包含1-8和x。

输出

输出移动的方案,用D,L,R,U表示。

D表示把x与它下面的数字交换

L表示把x与它左边的数字交换

R表示把x与它右边的数字交换

U表示把x与它上面的数字交换

如果有多个答案,输出字典序最小的方案。

字典序 D< L < R < U

如果不可能实现,输出-1。

样例输入

2  3  4  
1  5  x  
7  6  8

样例输出

DLURULLDDRURDLLURDR

在刚刚看到这道题目的时候,笔者很自然的想到了广搜。于是得到了一层层的往下迭代,判重的最朴素的思路。

在搜索中,其展开的每一个节点,就对应着八数码分布的一个状态,这样一层层的找下去,我们就能够找到一条路径,显然这条路径是最短的。

于是我们得到了下面的搜索思路。

while(/*队列非空*/){
	//生成下一层的节点,并判重 
	//将其与正确状态比较,如对直接输出 
	// 否则将其入队 
}

下面我们这个问题的关键就转成了如何判重。

由于只有九个数,于是笔者愚蠢地想到,可以将每一种状态转化为一个九位的整数,放在vis数组中存。这样我们需要1000000000个int的空间,但实际上,这样做的话就会有大量的空间被浪费。


于是笔者又愚蠢的想了很多种优化的方法,如转化为九进制等等都没起到什么大的作用。


现在让我们再回顾一下此题,它无非是0123456789这些数的排列,实际上只有9!=326880种情况,于是我们可以发明一种编码来储存这种状态,这是很自然的的思路。

bool vis[362880];
int fact[9]={0,1,2,6,24,120,720,5040,40320};  
int code=0;
int pc(node w){
	code=0;
	for(int i=0;i<9;i++){
		int x1=i/3+1;
		int y1=i%3+1;
		int ct=0; 
        for(int j=i+1;j<9;j++){
        	int x2=j/3+1;
        	int y2=j%3+1;
        	if(w.map[x1][y1]<w.map[x2][y2])ct++; 
    	} 
        code+=fact[8-i]*ct; 
	}
	return code;
}

这里将八数码的每一种状态都转化为了一个整数与之对应。

那我们剩下的最后一个问题就成了-----“这个八数码一定有解吗?”

笔者数学能力实在有限,只得上网求助。

这里直接附上大牛的链接


现在我们不难写出完整代码

#include<bits/stdc++.h>//八数码问题 by zryabc 
using namespace std;
struct node{
	int x,y,step;
	char Lj[1005];
	int map[4][4];
}F;
int cnt=0;
string in[4][4];
int R[4][4]={0,0,0,0,0,1,2,3,0,4,5,6,0,7,8,0};//正确的状态 
char sh[4]={'D','L','R','U'};//标记数码移动的动作 
int dxy[4][2]={1,0,0,-1,0,1,-1,0};//预处理x坐标的变化 
bool vis[362880];
int fact[9]={0,1,2,6,24,120,720,5040,40320};  
int code=0;
int pc(node w){//判重的部分 
	code=0;
	for(int i=0;i<9;i++){
		int x1=i/3+1;
		int y1=i%3+1;
		int ct=0; 
        for(int j=i+1;j<9;j++){
        	int x2=j/3+1;
        	int y2=j%3+1;
        	if(w.map[x1][y1]<w.map[x2][y2])ct++; 
    	} 
        code+=fact[8-i]*ct; 
	}
	return code;
}
bool check(node w){//判断是不是正确 
	for(int i=1;i<=3;i++){
		for(int j=1;j<=3;j++){
			if(w.map[i][j]!=R[i][j])return false;
		} 
	}
	return true;
}
void bfs(){//广搜部分 
	queue<node>q;
	node p=F;
	q.push(p);
	while(!q.empty()){
		node e=q.front();q.pop();
		for(int i=0;i<=3;i++){
			node next=e;
			next.x=e.x+dxy[i][0];
			next.y=e.y+dxy[i][1];
			if(next.x>=1 && next.x<=3 && next.y>=1 && next.y<=3){
				swap(next.map[next.x][next.y],next.map[e.x][e.y]);
			}
			else continue;
			int m=pc(next);	
			if(vis[m])continue;
			vis[m]=1;
			if(check(next)){
				next.Lj[e.step+1]=sh[i];
				cnt=e.step+1;
				for(int i=1;i<=cnt;i++)printf("%c",next.Lj[i]); 
				return;
			}
			else{
				next.step=e.step+1;
				next.Lj[next.step]=sh[i];
				q.push(next);
			}
		}
	}
}
int main(){
	int pd[10];//用于判断其是否有解 
	int a=0;
	for(int i=1;i<=3;i++){
		for(int j=1;j<=3;j++){
			cin>>in[i][j];
			if(in[i][j][0]=='x'){
				F.x=i;
				F.y=j;
				F.map[i][j]=0;
				pd[++a]=0;
			}
			else {
				F.map[i][j]=in[i][j][0]-'0';
				pd[++a]=in[i][j][0]-'0';
			}
		}
	}
	int Cnt=0;
	for(int i=1;i<=9;i++){
		for(int j=1;j<i;j++){
			if(pd[i]<pd[j] && pd[i]!=0)Cnt++;
		}
	}
	if(Cnt%2==1){
		printf("-1");
		return 0;
	}
	F.step=0;
	bfs();
	return 0;
}


阅读更多
换一批

没有更多推荐了,返回首页