八數碼問題
題目:洛谷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;
}