【雙向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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章