【双向BFS】八数码问题


题目:洛谷P1379八数码难题

题目描述

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

思路

Step1:哈希每个排列组合数

因为只有0-8共9个数字,所有可能的状态数等于9个数的排列数,即362880个。因为数不多,所以我们可以把每个排列所得的数装到一个int里,分配给每个组合一个id,也就是哈希一下。在求全排列我们可以通过dfs,也可以通过STL的next_permutation()函数。

代码:分配id

const int N=400000;
bool vis[10];
int num[N];
int id;
void dfs(int x, int sum) {
	if ( x==9 ) {
		id++;
		num[id]=sum;
		return;
	}
	for(int i=0; i<9; i++) {
		if (vis[i]) continue;
		vis[i]=1;
		dfs(x+1,sum*10+i);
		vis[i]=0;
	}
}

因为排列数是递增的,所以每次在求该组合的 id 时可以用二分法快速取得。

代码:查询每个组合数对应的id

int get_id(int n) {
	int l=1, r=362880;
	while(l<=r) {
		int mid=l+(r-l)/2;
		if ( n==num[mid] ) return mid;
		else if ( n<num[mid] ) r=mid-1;
		else l=mid+1;
	}
}

Step2:考虑如何把这个组合数转换成3x3的模式以及从3x3模式转换为组合数模式

在求出该状态的3x3地图模式时,顺便可以把0的位置求出来。
注意:因为是从末开始取的,所以在建立3x3图的形式的时候,要逆着建

代码:转换成3x3模式并求出0的座标

int mp[5][5]
int x_0,y_0;
void get_0(int n) {
	for(int i=3; i>=1; i--) {
		for(int j=3; j>=1; j--) {
			mp[i][j]=n%10;
			if ( mp[i][j]==0 ) x_0=i, y_0=j;
			n/=10;
		}
	}
}

代码:从3x3模式转换为组合数模式

int get_num() {
	int sum=0;
	for(int i=1; i<=3; i++) {
		for(int j=1; j<=3; j++) {
			sum=sum*10+mp[i][j];
		}
	}
	return sum;
}

Step3:用双向BFS求得最少步骤

每次得到这个组合数时,枚举0得四个方向,然后记录交换后的状态。
因为起点和终点都是明确的,所以可以直接双向BFS加速!
我们实现这一部分功能之前先思考需要什么:用 f [ ] 数组来记录路径,用到 get_0() 函数得到图并得到0的位置,枚举四个方向 dir[4][2] 数组,每次还会用到 get_id() 函数来得到 id,一个**check()**函数来检验0的交换是否越界。剩余一些小细节会在代码中注释
注意:每次交换0的位置以后还需交换回来

代码:求得最少步骤

int f[N];
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};

bool check(int x, int y) {
	return x>=1 && x<=3 && y>=1 && y<=3;
}

int bfs(int p1, int p2) {
	queue<int> q;
	f[p2]=-1, f[p1]=1;		//双向dfs起点和终点标记。
	q.push(p1); q.push(p2);
	while(!q.empty()) {
		int u=q.front(); q.pop();
		get_0(num[u]);		//得到当前排列的3x3模式以及0的位置
		for(int i=0; i<4; i++) {		//枚举四个交换方向
			int nx=x_0+dir[i][0], ny=y_0+dir[i][1];
			if ( check(nx,ny) ) {
				swap(mp[x_0][y_0],mp[nx][ny]);		//进行交换
				int p=get_id(get_num());		//得到交换后的id
				if ( !f[p] ) {		//如果这个状态没有到达过就进行更新
					//双向BFS,如果从起点过来就是正数,终点过来就是负数
					//用这个Abs函数巧妙解决问题
					f[p]=f[u]+f[u]/abs(f[u]);		
					q.push(p);
				}
				//如果乘积为负数,那么两点相交,找到最少步骤
				else if ( f[p]*f[u]<0 ) {		
					return abs(f[p]-f[u])-1;
				}
				//注意每次交换以后要交换回来!
				swap(mp[x_0][y_0],mp[nx][ny]);		
			}
		}
	}
	return -1;		//返回-1没找到(debug的时候用)
}

完整代码

//洛谷P1379-八数码难题
#include<bits/stdc++.h>
using namespace std;

const int N=400000;
const int Ans=123804765;
const int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
bool vis[10];
int f[N];
int num[N];
int id=0;
int mp[5][5];
int x_0,y_0;

void dfs(int x, int sum) {
	if ( x==9 ) {
		id++;
		num[id]=sum;
		return;
	}
	for(int i=0; i<9; i++) {
		if (vis[i]) continue;
		vis[i]=1;
		dfs(x+1,sum*10+i);
		vis[i]=0;
	}
}

int get_id(int n) {
	int l=1, r=362880;
	while(l<=r) {
		int mid=l+(r-l)/2;
		if ( n==num[mid] ) return mid;
		else if ( n<num[mid] ) r=mid-1;
		else l=mid+1;
	}
}

void get_0(int n) {
	for(int i=3; i>=1; i--) {
		for(int j=3; j>=1; j--) {
			mp[i][j]=n%10;
			if ( mp[i][j]==0 ) x_0=i, y_0=j;
			n/=10;
		}
	}
}

bool check(int x, int y) {
	return x>=1 && x<=3 && y>=1 && y<=3;
}

int get_num() {
	int sum=0;
	for(int i=1; i<=3; i++) {
		for(int j=1; j<=3; j++) {
			sum=sum*10+mp[i][j];
		}
	}
	return sum;
}

int Abs(int x) {
	return x>0?x:-x;
}

int bfs(int p1, int p2) {
	queue<int> q;
	f[p2]=-1, f[p1]=1;
	q.push(p1); q.push(p2);
	while(!q.empty()) {
		int u=q.front(); q.pop();
		get_0(num[u]);
		for(int i=0; i<4; i++) {
			int nx=x_0+dir[i][0], ny=y_0+dir[i][1];
			if ( check(nx,ny) ) {
				swap(mp[x_0][y_0],mp[nx][ny]);
				int p=get_id(get_num());
				if ( !f[p] ) {
					f[p]=f[u]+f[u]/Abs(f[u]);
					q.push(p);
				}
				else if ( f[p]*f[u]<0 ) {
					return Abs(f[p]-f[u])-1;
				}
				swap(mp[x_0][y_0],mp[nx][ny]);
			}
		}
	}
	return -1;
}

void solve() {
	dfs(0,0);
	int n;
	scanf("%d",&n);
	int p1=get_id(n), p2=get_id(Ans);
	if ( p1==p2 ) {
		printf("0\n");
		return;
	}
	printf("%d\n",bfs(p1,p2));
}

int main() {
//	freopen("in.txt","r",stdin);
	solve();
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章