历届试题 九宫重排

题目链接 

  如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。

  我们把第一个图的局面记为:12345678.
  把第二个图的局面记为:123.46758
  显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
  本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。

分析:八数码问题,用bfs搜索,可以用10进制数计录每一位的数字进行状态压缩。如(12345678. 可以用123456780表示。实际上可以用9进制数表示,但是10进制比9进制差不多。)接下来最重要的就是判重。

方法一:set判重 。因为c++中set的本质是一棵树,所以查找效率不高。(hashset不能用。。。)

#include 
#include 
#include 
#include 
using namespace std;
const int dx[4] = {0,-1,0,1}, dy[4] = {1,0,-1,0};
int start[9], goal[9], goalNum;
struct Status{
	int cells[9], dist;
};
set vis;
int getNum(int *a){
	int n = 0;
	for(int i = 0; i < 9; i++)  n = n*10+a[i]; //把状态转化成9位十进制整数
	return n;
}
 
int bfs(){
	int cnt = 0;
	queue q;
	Status u, v;
	memcpy(u.cells, start, sizeof(start));
	u.dist = 0;
	q.push( u);
	vis.insert( getNum(start));
	while( !q.empty()){
		u = q.front(); q.pop();
		int z;
		for(z = 0; z < 9; z++) if( !u.cells[z]) break; //找"0"的位置
		int x = z/3, y = z%3;  //获取行列编号(0~2)。(x,y)
		for(int i = 0; i < 4; i++){
			int nx = x+dx[i];
			int ny = y+dy[i];
			if( nx >= 0 && ny >= 0 && nx < 3 && ny < 3){
				memcpy(v.cells, u.cells, sizeof(goal));
				int nz = nx*3+ny;
				v.cells[nz] = u.cells[z];
				v.cells[z] = u.cells[nz];
				int n = getNum(v.cells);
				if( n == goalNum) return u.dist+1; //找到目标状态,成功返回
				v.dist = u.dist+1;								
				if( !vis.count(n)){
					vis.insert(n);
					q.push( v);
				} 
			}
		}
	}
	return -1;
}
 
int main(int argc, char** argv) {
	string sa, sb;
	cin>> sa>> sb;
	for(int i = 0; i < 9; i++){
		sa[i] != '.' ?  start[i] = sa[i]-'0' : start[i] = 0;
		sb[i] != '.' ?  goal[i] = sb[i]-'0'  :  goal[i] = 0;
	}
	goalNum = getNum(goal);
	cout<< bfs();	
	return 0;
}

方法二: hash判重。使用哈希(hash)技术。简单地说,就是要把结点“变成”整数,但不必是一一对应。换句话说,只需要设计一个所谓的哈希函数h(x),然后将任意结点x映射到某个给定范围[ 0 , M-1]的整数即可,其中M是程序员根据可用内存大小自选的。在理想情况下,只需开一个大小为M的数组就能完成判重,但此时往往会有不同结点的哈希值相同,因此需要把哈希值相同的状态组织成链表。

原文链接:https://blog.csdn.net/qq_42835910/article/details/86489107

#include
using namespace std;
typedef int State[9]; //定义"状态"类型
const int maxstate = 1000000;
State st[maxstate], goal; //状态数组。所有状态都保存在这里
int dist[maxstate]; //距离数组
//如果需要打印方案,可以在这里加一个"父亲编号"数组 int fa[maxstate]
const int dx[ ] = {-1, 1, 0, 0};
const int dy[ ] = {0, 0, -1, 1};
//BFS,返回目标状态在st数组下标
 
const int hashsize = 1000003;
int head[hashsize], next[maxstate];//next数组实现链表的功能。
void init_lookup_table( ) { memset(head, 0, sizeof(head)); }
int hash(State& s){
	int v = 0;
	for(int i = 0; i < 9; i++) v = v * 10 + s[i];//把9个数字组合成9位数
	return v % hashsize; //确保hash函数值是不超过hash表的大小的非负整数
}
bool try_to_insert(int s){
	int h = hash(st[s]);
	int u = head[h]; //从表头开始查找链表
	while(u){
	if(memcmp(st[u],st[s], sizeof(st[s]))==0)return false; //找到了,插入失败
	u = next[u]; //顺着链表继续找
	}
	next[s] = head[h]; //插入到链表中
	head[h] = s;
	return true;
}
 
int bfs( ) {
	init_lookup_table( ); //初始化查找表
	int front = 1, rear = 2; //不使用下标0,因为0被看作"不存在"
	while(front < rear) {
		State& s = st[front]; //用"引用"简化代码
		if(memcmp(goal, s, sizeof(s)) == 0) return front;//找到目标状态,成功返回
		int z;
		for(z = 0; z < 9; z++) if(!s[z]) break; //找"0"的位置
		int x = z/3, y = z%3; //获取行列编号(0~2)
		for(int d = 0; d < 4; d++) {
			int newx = x + dx[d];
			int newy = y + dy[d];
			int newz = newx * 3 + newy;
			if(newx >= 0 && newx < 3 && newy >= 0 && newy < 3){ //如果移动合法
				State& t = st[rear];
				memcpy(&t, &s, sizeof(s)); //扩展新结点
				t[newz] = s[z];
				t[z] = s[newz];
				dist[rear] = dist[front] + 1; //更新新结点的距离值
				if(try_to_insert(rear)) rear++; //如果成功插入查找表,修改队尾指针
			}
		}
		front++; //扩展完毕后再修改队首指针
	}
	return 0; //失败
}
 
int main( ){
string sa, sb;
	cin>> sa>> sb;
	for(int i = 0; i < 9; i++){
		sa[i] != '.' ?  st[1][i] = sa[i]-'0' : st[1][i] = 0;
		sb[i] != '.' ?  goal[i] = sb[i]-'0'  :  goal[i] = 0;
	}
	int ans = bfs( ); //返回目标状态的下标
	if(ans > 0) printf("%d\n", dist[ans]);
	else printf("-1\n");
	return 0;
}

输入格式

  输入第一行包含九宫的初态,第二行包含九宫的终态。

输出格式

  输出最少的步数,如果不存在方案,则输出-1。

样例输入

12345678.
123.46758

样例输出

3

样例输入

13524678.
46758123.

样例输出

22

你可能感兴趣的:(暴力搜索,蓝桥杯)