上回在介绍bfs时提到过这一道bfs经典题,现在我们来看一看~
八数码难题
题目描述
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
输入输出格式
输入格式:
输入初始状态,一行九个数字,空格用0表示。
输出格式:
只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)。
输入输出样例
输入样例#1:
283104765
输出样例#1:
4
做法:
1.bfs优化
2.双向bfs(本篇重点介绍)
3.A*(启发式搜索)
题解
核心:双向BFS + 哈希判重
这是一道公认的练习BFS的经典题
如果普通的BFS写得好的话也可以过这道题,但是由于这道题满足从结束状态反向扩展的条件,所以我选择了更省时也更省空间的
双向BFS ヽ( ̄▽ ̄)ノ
判重也选择了比map等STL更省时的
哈希判重  ̄ω ̄=
在实现时应注意可以使用占空间较小的一维状态存储到队列中,进行状态转移时先将小状态转化成易于操作但占空间较大的二维状态,转移完成再转化成易于存储的一维状态存入队列(当然dalao们也可以直接对一维状态进行转移,不过笔者觉得有些麻烦且不直观)。
进行哈希判重是为了减小判重数组占用空间;众所周知,如果用于取模的质数不够大,则可能会产生哈希冲突;但取模的质数过大则需开更大的判重数组,与原意相悖。所以笔者采用了双哈希判重,这样可以有效降低哈希冲突的概率(也可以采用三哈希甚至四哈希判重,四哈希判重的哈希冲突概率几乎可以忽略不计)。
详情请见代码。
代码如下
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
const int HASH = 10007;
const int HASH2 = 10037;
int head1 = 0, head2 = 0;
int tail1 = 1, tail2 = 1;
int pan1[2][10039];
int pan2[2][10039];
const int mubiao = 123804765 % HASH;
int map1[3][3];
int map2[3][3] = {{1, 2, 3}, {8, 0, 4}, {7, 6, 5}};
int dx[5] = {0, 1, -1, 0, 0};
int dy[5] = {0, 0, 0, 1, -1};
struct duilie
{
int heap;
int step; //用于记录BFS的层数
};
duilie a1[87390]; //用于BFS的两个队列
duilie a2[87390];
int x1, x2; //一系列的x,y用于转移状态
int y1, y2;
int linshi;
int lx1, ly1, lx2, ly2;
int ans1 = 0;
int ans2 = 0;
template<class T>void read(T &x) //读入优化
{
int f = 0; x = 0; char ch = getchar();
while(ch < '0' || ch > '9') f |= (ch == '-'), ch = getchar();
while(ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
x = f? -x : x;
}
void write(int x) //输出优化
{
if(x < 0) {putchar('-'); x = -x;}
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
void chuli(int aa[3][3], int b, int &x, int &y) // 把用9位数存储的状态展开为二维平面状态并记录当前0的位置,这样可以方便BFS中状态的转移
{
for(int i = 2; i >= 0; i--)
for(int j = 2; j >= 0; j--)
{
aa[i][j] = b % 10;
if(aa[i][j] == 0)
{
x = i;
y = j;
}
b /= 10;
}
}
int chuli2(int aa[3][3]) //把二维平面的数据状态转化为9位数的一维数据状态,方便状态在BFS队列中的存储
{
int x = 0;
for(int i = 0; i <= 2; i++)
for(int j = 0; j <= 2; j++)
{
x = x * 10 + aa[i][j];
}
return x;
}
void change(int &a, int &b){int c = a; a = b; b = c;} //用于交换值的函数(笔者习惯手写某些STL)
void bfs()
{
do //BFS
{
head1++;head2++; //队首指针
chuli(map1, a1[head1].heap , x1, y1); //把压缩成9位数的一维状态转化为易于处理的二维平面状态
chuli(map2, a2[head2].heap , x2, y2);
for(int i = 1; i <= 4; i++) //0可以向4个方向移动(正向BFS)
{
lx1 = x1 + dx[i]; ly1 = y1 + dy[i]; //0位置的移动
if(lx1 >= 0 && lx1 <= 2 && ly1 <= 2 && ly1 >= 0) //满足转移条件
{
change(map1[lx1][ly1], map1[x1][y1]); //交换二维平面上0初始位置与移动后位置上的值
linshi = chuli2(map1); //把二维平面状态下的数据转化为易于存储的9位数一维数据
if(pan1[0][linshi % HASH] == 0 || pan1[1][linshi % HASH2] == 0) //利用哈希判断这个状态是否在之前出现过,若否,则存储
{
tail1++; //尾指针++
a1[tail1].heap = linshi; //存入状态
a1[tail1].step = a1[head1].step + 1; //记录当前BFS结点扩展层数
pan1[0][linshi % HASH] = 1; //记录该状态哈希值
pan1[1][linshi % HASH2] = 1; //双哈希以排除哈希冲突
}
if(pan2[0][linshi % HASH] == 1 && pan2[1][linshi % HASH2] == 1) //如果在反向BFS存在现在这种状态,则表明已经找到答案,所以输出答案并终止程序
{
for(int i = 1; i <= tail2; i++) //寻找正向BFS与反向BFS发生状态重复时反向BFS的扩展层数
{
if(a2[i].heap == linshi)
{
int ans = a2[i].step + a1[tail1].step; //计算答案
write(ans); //输出答案
exit(0); //直接干净利索不带一丝迟疑的结束程序
}
}
}
change(map1[lx1][ly1], map1[x1][y1]); //由于在下一次计算中还要用到这个二维数据,所以把刚才换掉的再换回来
}
lx2 = x2 + dx[i]; ly2 = y2 + dy[i];
if(lx2 >= 0 && lx2 <= 2 && ly2 <= 2 && ly2 >= 0) //反向BFS从终止状态开始扩展,主体与正向大致相同,变量和终止条件换换即可
{
change(map2[lx2][ly2], map2[x2][y2]);
linshi = chuli2(map2);
if(pan2[0][linshi % HASH] == 0 || pan2[1][linshi % HASH2] == 0)
{
tail2++;
a2[tail2].heap = linshi;
a2[tail2].step = a2[head2].step + 1;
pan2[0][linshi % HASH] = 1;
pan2[1][linshi % HASH2] = 1;
}
if(pan1[0][linshi % HASH] == 1 && pan1[1][linshi % HASH2] == 1)
{
for(int i = 1; i <= tail1; i++)
{
if(a1[i].heap == linshi)
{
int ans = a2[tail2].step + a1[i].step;
write(ans);
exit(0);
}
}
}
change(map2[lx2][ly2], map2[x2][y2]);
}
}
}while(head1 < tail1 || head2 < tail2);
}
int main()
{
a1[1].step = 0; //初始扩展层数记为0
a2[1].step = 0;
int a;
read(a); //由题意输入起始状态
a1[1].heap = a; //初始状态
a2[1].heap = 123804765; //终止状态
chuli(map1, a, x1, y1);
bfs();
return 0;
}
(由于单语句较长,代码较丑,请见谅)