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 八数码的主要内容,如果未能解决你的问题,请参考以下文章