八数码问题-记三种思路

八数码问题的难点是如何存储状态,进而判断某个状态是否访问过,这里记录一下我见到的几种方法

思路一 :bfs + unordered_map

#include<iostream>
#include<algorithm>
#include<unordered_map>
#include<queue>

using namespace std;

int bfs(string state){
	queue<string> q;
	unordered_map<string, int> d;//字符串(每个状态) → 整数(距离)( 
	
	q.push(state);
	d[state] = 0;
	
	int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
	
	string end = "12345678x";
	while (q.size()){
		auto t = q.front();
		q.pop();
		
		if (t == end) return d[t];
		
		int dis = d[t];
		int k = t.find('x');//定位‘x’ 
		int x = k / 3, y = k % 3;
		
		for (int i = 0; i < 4; i ++ ){
			int nx = x + dx[i], ny = y + dy[i];
			if (nx >= 0 && nx < 3 && ny >= 0 && ny < 3){
				swap(t[nx * 3 + ny], t[k]);//移动x
				if (!d.count(t)){
					d[t] = dis + 1;
					q.push(t);
				} 
				swap(t[nx * 3 + ny], t[k]);//回溯 
			} 
		}
	}
	return -1;// 如果不存在解决方案,则输出 -1。
}

int main(){
	char s[2];//空格也要考虑 
	
	string state;
	for (int i = 0; i < 9; i ++ ){
		cin >> s;
		state += *s;//忽略空格 
	}
	cout << bfs(state) << endl;
	return 0;
}
/*
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/48146/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/

思路二:bfs + 康托展开


#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
const int LEN = 362880;//9的阶乘,总状态数
struct node {
	int state[9];//记录一个八数码序列,即一个状态
	int dis;//记录到起点的距离,即步数
};
int dir[4][2] = { {-1,0},{0,-1},{1,0},{0,1} };;//向左,向上,向右,向下。x轴向右,y轴向下,原点在左上角。

int visited[LEN] = { 0 };//记录每个状态, Cantor函数对它置数, 并且判重
int start[9];
int goal[9];
long int factory[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 };//Cantor函数需要用到的常数

bool Cantor(int str[], int n) {
	long result = 0;
	for (int i = 0; i < n; i++) {
		int counted = 0;
		for (int j = i + 1; j < n; j++) {
			if (str[i] > str[j]) {
				++counted;
			}
			result += counted * factory[n - i - 1];
		}
	}
	if (!visited[result]) {
		visited[result] = 1;
		return 1;
	}
	else
		return 0;
}
int bfs() {
	node head;
	memcpy(head.state, start, sizeof(head.state));//复制起点状态
	head.dis = 0;
	queue<node> q;
	Cantor(head.state, 9);
	q.push(head);

	while (!q.empty()) {
		head = q.front();
		if (memcmp(head.state, goal, sizeof(goal)) == 0) {
			return head.dis;
		}
		q.pop();
		int z;
		for (z = 0; z < 9; z++) {//寻找0的位置(一维的)
			if (head.state[z] == 0) {//找到了!
				break;
			}
		}
		//接着把状态转换成二维的
		int x = z % 3;
		int y = z / 3;
		for (int i = 0; i < 4; i++) {
			int newx = x + dir[i][0];
			int newy = y + dir[i][1];//生成新的0的位置,即模拟二维,调整一维。
			int nz = newx + 3 * newy;//转换为一维的
			if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3) {
				node newnode;
				memcpy(&newnode, &head, sizeof(struct node));//复制新状态
				swap(newnode.state[z], newnode.state[nz]);//把0移动到新的位置,在一维上操作免去了把一个二维数组麻烦地变为一维的
				newnode.dis++;
				if (Cantor(newnode.state, 9)) {
					q.push(newnode);
				}
			}
		}
	}
	return -1;
}

int main(void) {
	for (int i = 0; i < 9; i++) {
		cin >> start[i];
	}
	for (int i = 0; i < 9; i++) {
		cin >> goal[i];
	}
	int num = bfs();
	if (num != -1) {
		cout << num << endl;
	}
	else {
		cout << "-1" << endl;
	}
	return 0;
}
//来源:算法竞赛从入门到进阶

思路三:双向bfs + unordered_map

#include<bits/stdc++.h>	
#include<unordered_map>	
using namespace std;
int mat[3][3] = { 0 };//记录棋盘的一次性的全局变量,可以节省很多操作
int zx, zy;//记录0的位置的一次性的全局变量,可以节省很多操作
int dx[4] = { -1, 0, 1, 0 };
int dy[4] = { 0, -1, 0, 1 };
#define CHECK(x, y) (x >= 0 && x < 3 && y < 3 && y >= 0)
queue<int> q0, q1;
unordered_map <int, int> state;
unordered_map <int, int> ans;
void itom(int x) {//将一个九位数(棋盘)变成一个3乘3的矩阵
	int div = 100000000;
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			mat[i][j] = (x / div)%10;
			if (!mat[i][j]) {//顺便记录0的位置
				zx = j;
				zy = i;
			}
			div = div / 10;
		}
	}
}
int mtoi(void) {//将一个矩阵变成一个九位整数
	int chs = 0;
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			chs = chs * 10 + mat[i][j];
		}
	}
	return chs;
}
void dbfs(int start, int end) {
	if (start == end) {//特殊情况:起点即终点
		cout << "YES!";
		cout << 0;
		return;
	}
	int flag, now;//flag用来判断新状态要加入哪个队列,now是队头
	state[start] = 1, state[end] = 2;
	ans[start] = 0, ans[end] = 0;
	q0.push(start), q1.push(end);//双向bfs,双倍的初始化
	while (!q0.empty() && !q1.empty()) {//得用&&还是||---前者
		if (q0.size() > q1.size()) {//谁的元素少,就先操作谁
			now = q1.front(), q1.pop();
			itom(now);//把九位整数的队头转换为矩阵,同时获得0的坐标zx, zy
			flag = 1;
		}
		else {
			now = q0.front(), q0.pop();
			itom(now);
			flag = 0;
		}
		for (int i = 0; i < 4; i++) {
			int nx = zx + dx[i];
			int ny = zy + dy[i];
			if (CHECK(nx, ny)) {
				swap(mat[zy][zx], mat[ny][nx]);//0的移动
				int k = mtoi();//矩阵转数值
				if (!ans.count(k)) {//若未访问过,紧跟三个更新
					state[k] = state[now];//标记更新
					ans[k] = ans[now] + 1;//路程更新
					if (flag == 0) {//队列更新
						q0.push(k);
					}
					else if (flag == 1) {
						q1.push(k);
					}
				}
				else if(state[k] + state[now] == 3){//若已访问过
					int cnt = ans[k] + ans[now] + 1;//这个+1可以画画图
					cout << "YES!" << endl;
					cout << cnt << endl;
					return;
				}
			}
			swap(mat[zy][zx], mat[ny][nx]);//回溯,恢复现场,恢复了现场才能进行其它的操作
		}
	}
	cout << "NO" << endl;
	return;
}

int main(void) {
	/*测试矩阵与整数之间的相互转换
	itom(876543021);
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			cout << mat[i][j] << " ";
		}
		cout << endl;
	}
	cout << mtoi();
	*/
	int start = 0;
	//start = 283104765;答案是4
	int end = 123804765;
	for (int i = 0; i < 9; i++) {
		int j = 0;
		cin >> j;
		start = start * 10 + j;
	}
	dbfs(start, end);
	return 0;
}
//来源:算法竞赛从入门到进阶
posted @ 2021-11-29 16:23  tsrigo  阅读(61)  评论(0编辑  收藏  举报