AcWing 179 八数码

Posted 蒟蒻豆进阶之路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AcWing 179 八数码相关的知识,希望对你有一定的参考价值。

二、双向bfs解法

双向bfs题解
https://www.acwing.com/solution/content/43817/

八数码(双向广搜bfs解法)
https://www.bilibili.com/video/BV185411A7nG?spm_id_from=333.999.0.0

八数码(代码落实详细讲解)
https://www.bilibili.com/video/BV1ib4y1D7uc?spm_id_from=333.999.0.0

#include <bits/stdc++.h>

using namespace std;

const int N = 5;
typedef unordered_map<string, pair<char, string>> MSP;
typedef unordered_map<string, int> MSI;
const char op[] = \'u\', \'d\', \'l\', \'r\';
// apre记录从起来出来的路径和转化方式
MSP aPre, bPre;
// da 表示从起点出来的字符串到起点的距离 db表示从终点出来的距离终点的距离
MSI da, db;
string mid;           //中间状态
queue<string> qa, qb; //两个队列,分别用于存放从起点走出来的字符串和从终点走出来的字符串
char g[N][N];         //用于计算变化操作的字符数组
int cnt;              //已经进行了的搜索次数
//将字符串转换为字符数组以便进行变化操作
void setStr(string s) 
    for (int i = 0; i < 3; i++)
        g[0][i] = s[i], g[1][i] = s[i + 3], g[2][i] = s[i + 6];

//这个是将我们变化后的数组转回字符串进行后续操作
string getStr() 
    string res;
    for (int i = 0; i < 3; i++)
        res += g[i][0], res += g[i][1], res += g[i][2];
    return res;


string up(string s)  //第一种变化方式 u
    setStr(s); //先把字符串转化为数组便于操作
    int x, y;  //用于记录当前x,y的位置
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            if (g[i][j] == \'x\' && i == 0)
                return "x"; //如果在边界上,说明不能走,返回x表示不能走
            else if (g[i][j] == \'x\' && i != 0)
                x = i, y = j; //如果可以走得话,记录一下下标,然后操作
    swap(g[x][y], g[x - 1][y]);
    return getStr(); //返回操作后的字符串

string down(string s)  //第二种变化方式 d
    setStr(s);          //先把字符串转化为数组便于操作
    int x, y;           //用于记录当前x的位置
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            if (g[i][j] == \'x\' && i == 2)
                return "x"; //如果在边界上,说明不能走,返回 x 表示不能走
            else if (g[i][j] == \'x\' && i != 2)
                x = i, y = j; //如果可以走得话,记录一下下标,然后操作
    swap(g[x][y], g[x + 1][y]);
    return getStr(); //返回操作后的字符串

string left(string s)  //第三种变化方式 l
    setStr(s);          //先把字符串转化为数组便于操作
    int x, y;           //用于记录当前x的位置
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            if (g[i][j] == \'x\' && j == 0)
                return "x"; //如果在边界上,说明不能走,返回 x 表示不能走
            else if (g[i][j] == \'x\' && j != 0)
                x = i, y = j; //如果可以走得话,记录一下下标,然后操作
    swap(g[x][y], g[x][y - 1]);
    return getStr(); //返回操作后的字符串

string right(string s)  //第四种变化方式 r
    setStr(s);           //先把字符串转化为数组便于操作
    int x, y;            //用于记录当前x的位置
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            if (g[i][j] == \'x\' && j == 2)
                return "x"; //如果在边界上,说明不能走,返回 x 表示不能走
            else if (g[i][j] == \'x\' && j != 2)
                x = i, y = j; //如果可以走得话,记录一下下标,然后操作
    swap(g[x][y], g[x][y + 1]);
    return getStr(); //返回操作后的字符串


//扩展一下队内元素较少的那一个,能有效减少我们的算法实际运行效率
int extend(queue<string> &q, MSI &da, MSI &db, MSP &apre, MSP &bpre) 
    for (int i = 0; i < q.size(); i++) 
        string t = q.front(); //取出对头扩展
        q.pop();              //出队

        string st[] = up(t), down(t), left(t), right(t); //记录一下我们不同操作对应的结果状态
        for (int i = 1; i <= 4; i++) 
            string u = st[i - 1];
            //如果下一步的状态可达,并且没有访问过的话,走之
            if (u != "x" && !da[u]) 
                da[u] = da[t] + 1; //距离增加1
                apre[u] = op[i - 1], t;
                //如果当前的字符串在对方中已经被找到过了,那说明二者之间已经有了一个联系,那就可以结束寻找了
                if (db[u])                   //如果对方已经搜到了
                    mid = u;                  //将中间态保存到全局变量中,方便以后的操作
                    return da[u] + db[u] - 1; //返回中间点距离起点、终点距离和-1
                
                q.push(u); //放入队列进行扩展
            
        
    
    return -1; //如果本次扩展没有找到连接前后的字符串,那就返回-1表示还需要继续找


int bfs(string A, string B) 
    qa.push(A); //先将起点放入我们从起点开始扩展的队列,作为起点
    da[A] = 0;  //让起点距离起点的距离设置为0

    qb.push(B); //先将终点放入我们从终点开始扩展的队列,作为终点
    db[B] = 0;  //让终点距离终点的距离设置为0

    //当二者队列里面同时含有元素的时候,才满足继续扩展的条件
    //当某个队列搜索完毕,还没有拿到答案的话,就说明完毕的那个自已形成了闭环,
    //永远不可能与另一个相交,肯定是没有结果了
    while (qa.size() && qb.size()) 
        //神奇的特判操作,由于这题目的特殊性质,双方有可能搜了很久还是没搜到交集,
        //那么就其实说明无解了,其实这也是搜索的一种特殊处理办法,值得学习
        cnt++;
        if (cnt >= 20) return -1; //这个数字是黄海试出来的~

        int t;
        if (qa.size() <= qb.size()) //这里面是一个双向bfs的优化策略,两个队列谁小就谁使劲跑
            t = extend(qa, da, db, aPre, bPre); //从a中取状态进行扩展
        else
            t = extend(qb, db, da, bPre, aPre);

        if (t != -1) return t;
    
    return -1; //如果到最后都没有找到的话,那也说明无解了


int main() 
    //出发状态,目标状态
    string A, B = "12345678x";
    char x;
    for (int i = 1; i <= 9; i++) cin >> x, A += x; //生成起点

    int ans = bfs(A, B); //进行搜索

    if (ans == -1)
        printf("unsolvable"); //如果无解
    else                     //如果有解
        string res1, res2;    //前半段字符串,后半段字符串

        // A->mid的过程
        //因为每个节点记录的是前驱,所以需要从mid开始向回来推
        //为每每个节点记录的是前驱,而不是记录后继呢?因为每个节点可能最多有4个后继,
        //没有唯一性,而记录前驱有唯一性。
        string t = mid;        //中间状态
        while (t != A)        //找出我们从起点到中间点所经历的  操作(udlr的意思)
            res1 += aPre[t].first; //拼接出操作的符号udlr...
            t = aPre[t].second;    //向前一个状态字符串
        
        //由于我们所得到的顺序是从中间到起点的,那么我们需要的是从起点到中间的,直接倒一边就好了
        //注意这里只是顺序反了一下,操作是没问题的,因为我们实际上就是从起点到终点的
        //只不过我们取出来的时候是反向取的,和下面的操作有一些差别
        reverse(res1.begin(), res1.end());

        // B->mid的过程
        /*这一步操作需要特殊说明,由于我们实际上,也就是代码上实现的是从终点到中间点的,但是我们需要的是
        从中间点到终点的,它的转换其实不是一个简单的倒回去操作就可以实现的,比如,我们在前面解释过的例子
        我们需要把每个操作都反一下,才是回来的操作,也就是从中间点到终点的操作(前面已经解释)
        */
        t = mid;
        while (t != B)  //找出我们从终点到中间点所经历的  操作(udlr的意思)
            char cmd = bPre[t].first;
            if (cmd == \'u\' || cmd == \'d\')
                cmd = \'u\' + \'d\' - cmd;
            else
                cmd = \'l\' + \'r\' - cmd;
            res2 += cmd;
            t = bPre[t].second; //向后一个状态字符串
        
        //为什么后半段不需要转化呢?前面已经解释过了就不再赘述
        cout << res1 << res2; //最后输出两段合起来的就好了
    
    return 0;


以上是关于AcWing 179 八数码的主要内容,如果未能解决你的问题,请参考以下文章

[A*] aw179. 八数码(A*+bfs最小步数模型+模板题)

AcWing 845. 八数码

AcWing 845. 八数码(BFS)

845. 八数码

845. 八数码

[A*] aw178. 第K短路(A*+bfs最小步数模型+好题)