八数码问题(bfs+哈希)

白书上给出了灰常飘逸,灰常飘逸的八数码问题的解答……上次去HFUT参加他们软件学院的校赛,居然都是白书上的原题……竟然名字也没改“八数码问题”>-<,既然允许参考纸质资料,当时还不知道神码是八数码,神码是哈希的我,原封不动抄了上去A了它……惭愧不已

日后学习了这份代码,才知道这份代码有多么飘逸……据说八数码问题是A*(A-star)算法「静态路网`最短路`估价函数。。。」的模板题。。。参见百度百科;百度文库

下面给出题目:

编号为1~8的8个正方形滑块被摆成3行3列(有一个格子留空)。每次可以把与空格相邻的滑块(有公共边才算相邻)移到空格中,而它原来的位置就成为了新的空格。给定初始局面和目标局面(用0表示空格),你的任务是计算出最少的移动步数。如果无解,输出-1。

八数码问题(bfs+哈希)_第1张图片

Sample

Input

2 6 4 1 3 7 0 5 8

8 1 5 7 3 6 4 0 2

Output

31

————————————————————我是分割线sama————————————————————

思路:从第一步开始,我们每一步都有不多于四种选择:将白格向上移;向下移;向左移;向右移。这很像是做迷宫。走迷宫的任务是走到某一点就算赢。而八数码,是走到什么局面才算赢。那么队列中存放的就不是点,而是面。换句话说,就是图。现在问题变得很简单,只要你能在一张3×3的图上裸着BFS一遍,搜到目标图就算你赢了。就像迷宫的vis数组一样,我们需要对走过的局面设置标记,不要重复走。

但是问题依然很严峻:判重(判断局面是否重复)。将图(局面)看成若干结点,那么有9!=362880个结点。开数组这么办可不行。这时候要利用哈希(hash)技术压缩空间。对于Hash,参见:百度百科

解决了这个问题,本题就可解了。

首先bfs(),开个队列存图,开个dis数组存步数。每一次取队首那张图的白格,尝试四个方向的移动,然后判断局面是否重复,不重复就进队。

其次是哈希,对于哈希函数怎么写?随便写。随便写?没有相当的实力,你真的写不好。因为如果产生了碰撞,哈希表退化,效率会慢的惊人。参见《算法导论》。lrj写了一个完美的哈希:把九个编号组合成1个9位数。

最后是挂链和判重。避免使用指针,lrj开出一个head数组,一个next数组,其中head数组是每个哈希值的链表头指针,next数组就是链表的next指针。

代码如下:

#include 
#include 
#include 
const int N = 1000000, HN = 1000003;
int head[HN], next[N];//链表(用于哈希)
int st[N][9], goal[9];
int dis[N];
const int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};

int Hash(int *st) {
	int v = 0;
	for(int i = 0; i < 9; i++)
		v = v*10 + st[i];//恰如其分得将9个数字映射成9位数
	return v % HN;//确保hash值不超过hash表大小
}

bool try_insert(int rear) {
	int h = Hash(st[rear]);
	int u = head[h];
	while(u) {
		if(!memcmp(st[u], st[rear], sizeof(st[0])))
			return 0;//重复,不挂,返回假
		u = next[u];
	}
	next[rear] = head[h];//rear指向旧的head[h]
	head[h] = rear;//rear成为新的head[h],如此一来,就把rear插到链表的头上了
	return 1;
}

int bfs() {
	memset(head, 0, sizeof(head));//初始化查找表,其实就是表头们
	int fron = 1, rear = 2;
	while (fron < rear) {
		if (!memcmp(goal, st[fron], sizeof(st[0])))
		    return fron;//找到目标图
		int z;
		for(z = 0; z < 9; z++)
		if(!st[fron][z])//找到白格
			break;//更新z为队首的白格
		int x = z / 3, y = z % 3;
		for(int d = 0; d < 4; d++) {
			int nx = x + dx[d], ny = y + dy[d], nz = 3*nx + ny;
			if(nx >= 0&&nx < 3&&ny >= 0&&ny < 3) {//判断边界
				memcpy(&st[rear], &st[fron], sizeof(st[0]));
				st[rear][nz] = st[fron][z];
				st[rear][z] = st[fron][nz];//这是一次移动的尝试
				dis[rear] = dis[fron] + 1;
				if(try_insert(rear))//判重,若不重,则进队
					rear++;
			}
		}
		fron++;//完成队首的尝试,队首出队。这个bfs和普通的bfs不太一样,st[][]其实就是队列,就是很多张图
	}
	return 0;
}

int main() {
	freopen("test.in", "r", stdin);
	for(int i = 0; i < 9; i++)
		scanf("%d", &st[1][i]);
	for(int i = 0; i < 9; i++)
		scanf("%d", &goal[i]);
	int ans = bfs();
	if(ans > 0)
		printf("%d\n", dis[ans]);
	else
		puts("-1");
	return 0;
}


你可能感兴趣的:(哈希,BFS,模板)