[BFS]正向BFS+hash解决八码问题

10 篇文章 0 订阅
8 篇文章 0 订阅
八数码问题可以通过正向广度优先搜索(BFS)结合哈希策略求解。由于所有可能状态总数为9!,使用数组保存会超出内存限制。采用康托展开将排列转换为整数进行哈希,降低存储需求。BFS结束条件是当前状态的康拓展开等于1。代码实现中,数组作为队列保存状态,通过pre变量记录上一状态在队列中的位置,以输出搜索路径。
摘要由CSDN通过智能技术生成

八数码问题,可以用单向广搜、双向广搜、A*、IDA等多种方法求解。具体可以参考:八数码的八境界

Description

The 15-puzzle has been around for over 100 years; even if you don't know it by that name, you've seen it. It is constructed with 15 sliding tiles, each with a number from 1 to 15 on it, and all packed into a 4 by 4 frame with one tile missing. Let's call the missing tile 'x'; the object of the puzzle is to arrange the tiles so that they are ordered as: 
 1  2  3  4 

 5  6  7  8 

 9 10 11 12 

13 14 15  x 

where the only legal operation is to exchange 'x' with one of the tiles with which it shares an edge. As an example, the following sequence of moves solves a slightly scrambled puzzle: 
 1  2  3  4    1  2  3  4    1  2  3  4    1  2  3  4 

 5  6  7  8    5  6  7  8    5  6  7  8    5  6  7  8 

 9  x 10 12    9 10  x 12    9 10 11 12    9 10 11 12 

13 14 11 15   13 14 11 15   13 14  x 15   13 14 15  x 

           r->           d->           r-> 

The letters in the previous row indicate which neighbor of the 'x' tile is swapped with the 'x' tile at each step; legal values are 'r','l','u' and 'd', for right, left, up, and down, respectively. 

Not all puzzles can be solved; in 1870, a man named Sam Loyd was famous for distributing an unsolvable version of the puzzle, and 
frustrating many people. In fact, all you have to do to make a regular puzzle into an unsolvable one is to swap two tiles (not counting the missing 'x' tile, of course). 

In this problem, you will write a program for solving the less well-known 8-puzzle, composed of tiles on a three by three 
arrangement. 

Input

You will receive a description of a configuration of the 8 puzzle. The description is just a list of the tiles in their initial positions, with the rows listed from top to bottom, and the tiles listed from left to right within a row, where the tiles are represented by numbers 1 to 8, plus 'x'. For example, this puzzle 
 1  2  3 

 x  4  6 

 7  5  8 

is described by this list: 
 1 2 3 x 4 6 7 5 8 

Output

You will print to standard output either the word ``unsolvable'', if the puzzle has no solution, or a string consisting entirely of the letters 'r', 'l', 'u' and 'd' that describes a series of moves that produce a solution. The string should include no spaces and start at the beginning of the line.

Sample Input

 2  3  4  1  5  x  7  6  8 

Sample Output

ullddrurdllurdruldr

题解:

本题一共仅有9!种结果,因此求解方法很多。一开始采用stl进行存储,但一直超时,后来改用hash轻轻松松就过了。判重时9!个排列如果用数组直接保存,每一位保存一个维度,数组开不了那么大。因此可以根据康托展开进行判重,每一种排列对应成一个整形数字,9!种排列一共9!个数字,提高了hash效率。此外,对于x我们暂且当做9处理,而123456789的康托展开是1,因此bfs的终止条件就设为当前状态的康拓展开是否为1。此外,由于本次采用正向bfs,而输出结果时需要输出之前的状态,string储存太慢,queue队列会丢失之前的状态,因此用数组充当队列,用pre追溯上一个状态在队列中的下标。解决代码如下:

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
const int MAXN = 400000;
int fac[9] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320};
int dir[4][2] = { {-1,0},{1,0},{0,-1},{0,1} };
int opp[4] = { 'u','d','l','r' };
bool vis[MAXN];
struct node {
	int stadus;
	int cur[9];
	int loc;
	char path;
	int pre;
};
node qu[MAXN];
int cantor(int s[])
{
	int sum = 0;
	for (int i = 0; i<9; i++)
	{
		int num = 0;
		for (int j = i + 1; j<9; j++)
			if (s[j]<s[i])
				num++;
		sum += num*fac[8 - i];
	}
	return sum + 1;
}
int bfs(node now) {
	memset(vis, false, sizeof(vis));
	int x, y;
	int front = 0, end = 0;
	node no = now;
	qu[end++] = no;
	vis[now.stadus] = true;
	while (front < end) {
		no = qu[front++];
		x = no.loc / 3;
		y = no.loc % 3;
		if (no.stadus == 1)
			return front - 1;
		for (int i = 0; i < 4; i++) {
			node cc = no;
			int xx = x + dir[i][0];
			int yy = y + dir[i][1];
			if (xx < 3 && xx >= 0 && yy < 3 && yy >= 0) {
				cc.cur[x * 3 + y] = cc.cur[x * 3 + y] ^ cc.cur[xx * 3 + yy];
				cc.cur[xx * 3 + yy] = cc.cur[x * 3 + y] ^ cc.cur[xx * 3 + yy];
				cc.cur[x * 3 + y] = cc.cur[x * 3 + y] ^ cc.cur[xx * 3 + yy];
				cc.stadus = cantor(cc.cur);
				if (!vis[cc.stadus]) {
					vis[cc.stadus] = true;
					cc.loc = xx * 3 + yy;
					cc.path = opp[i];
					cc.pre = front - 1;
					qu[end++] = cc;
				}
			}
		}
	}
	return -1;
}
void show(int a) {
	if (qu[a].pre) {
		show(qu[a].pre);
	}
	printf("%c", qu[a].path);
}
int main() {
	string tmp;
	while (getline(cin, tmp)) {
		int i = 0, cnt = 0;
		node temp;
		while (tmp[i]) {
			if (tmp[i] == ' ') {
				i++;
				continue;
			}
			else if (tmp[i] == 'x') {
				temp.loc = cnt;
				temp.cur[cnt] = 9;
				cnt++;
				i++;
			}
			else {
				temp.cur[cnt] = tmp[i] - '0';
				cnt++;
				i++;
			}
		}
		temp.stadus = cantor(temp.cur);
		int ans = bfs(temp);
		ans != -1 ? show(ans) : printf("-1");
		cout << endl;
	}
	return 0;
}

include using namespace std; struct node{ int nodesun[4][4]; int pre; //上一步在队列中的位置 int flag ; //步数标识,表示当前的步数为有效的 int value; //与目标的差距 int x,y; //空格坐标 }queue[1000]; //移动方向数组 int zx[4]={-1,0,1,0}; int zy[4]={0,-1,0,1}; //当前步数 int top; int desti[4][4];//目标状态 int detect(struct node *p)//检查是否找到 {int i,j; for(i=1;i<4;i++) for(j=1;jnodesun[i][j]!=desti[i][j]) return 0; return 1; } //打印 void printlj() {int tempt; int i,j; tempt=top; while(tempt!=0) { for(i=1;i<4;i++) for(j=1;j<4;j++) {cout<<queue[tempt].nodesun[i][j]; if(j==3) cout<<" "<<endl; } tempt=queue[tempt].pre; } } //现在状态与目标状态有多少个不同位置 int VALUE(struct node *p) {int count=0; int i,j; for(i=1;i<4;i++) for(j=1;jnodesun[i][j]!=desti[i][j]) count++; return count; } void main() { //初始化 int i,j,m,n,f; int min=10; int temp,find=0,minnumber; top=1; for(i=1;i<4;i++) for(j=1;j<4;j++) {cout<<"请输入第"<<i<<"行"<<"第"<<j<<"列的值"<>temp; queue[1].nodesun[i][j]=temp; } cout<<"请输入初始状态的空格的位置(行)"<>temp; queue[1].x=temp; cout<<"请输入初始状态的空格的位置(列)"<>temp; queue[1].y=temp; queue[1].value=VALUE(&queue[1]); queue[1].pre=0; //上一步在队列中的位置 queue[1].flag=0; //目标状态 for(i=1;i<4;i++) for(j=1;j<4;j++) {cout<<"请输入目标状态第"<<i<<"行"<<"第"<<j<<"列的值"<>temp; desti[i][j]=temp; } //根据估价函数 while(!find&&top>0) { for(i=1;i<=top;i++) //////////////////////////////////////////// //min为上一图中与目标图有多少个元素不相同,queue[i]为当前图与目标图有多少个元素不相同通过这两个数的比较,就可以得出当前图较之上一图向目标图接近同时把当前的i记录下来进行下一步比较 {if(queue[i].value<min&&queue[i].flag==0) {minnumber=i;// min=queue[i].value; //还有多少不同的位数 } } queue[minnumber].flag=1; //表示此位有效 ////////////////////////////////////// // for(f=0;f=1&&i=1&&j<=3) {top++; ///////////////////////////////////////////// //位置交换 queue[top]=queue[minnumber]; queue[top].nodesun[m][n]=queue[minnumber].nodesun[i][j]; queue[top].nodesun[i][j]=0; /////////////////////////////////////// //空格移动方向 queue[top].x=i; queue[top].y=j; /////////////////////////////////////// queue[top].pre=minnumber; //上一步在队列中的位置 queue[top].value=VALUE(&queue[top]); //有多少位与目标不同 queue[top].flag=0; //标识位初始化 if(detect(&queue[top])) //检查是否为目标 {printlj(); //打印 find=1; //设找到标识位 break; } } } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值