【双向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;
}

猜你喜欢

转载自www.cnblogs.com/ZX-GO/p/12535717.html