算法设计与分析 第四周
滑动谜题
1.题目描述
2.选题原因
上一周做了图算法,本周学习了广度优先与深度优先,恰好上课遇到了
8数码
问题,在搜索题库后发现,有一道滑动谜题
,类似于5数码问题
,恰好是困难难度,因此想要尝试求解。
3.题目分析
广度优先求解问题的算法思路较为简单清晰,完全靠着暴力求解即可。本题目中,每一张图都用了一个
二维vector
来表示。如果按照广度优先的思路,这么多vector
,很容易爆炸,并且如此多的可能性,很难标记那些节点是已经到达过的。如果直接拿vector
来比较,时间复杂度将会是十分巨大的。
在bfs处理中,常常会采取使用使用数组标记的方法来对应不同的节点,这给了我们很好的思路。因为相比较于存储720个vector<vector<int> >
,并且不断地遍历查询来说,使用数组,并且直接通过角标来访问要快速很多。因此我们的问题转换为了如何将每一幅图一一映射到相对短的整数数组上。
最简单的方法当然是就按照顺序来存,每一位对应好个、十、百位,例如:[(1, 2, 3); (4, 5, 0)]
就转化为了123450
。当然,这样存储的数据量太过于大,因此,我们可以将10进制
化为6进制
来存储。
因此,对于排列:a b c d e f
=>a * 6 ^ 6 + b * 6 ^ 5 + c * 6 ^ 4 + d * 6 ^ 3 + e * 6 ^ 2 + f * 6 ^ 1
就将大小缩小到了原来的(6/10)^6
,约等于原先的0.0467
,大大缩短了长度。
4.算法
按照原先的思路,我们只需要按部就班的进行即可。
节点的数据结构包括4位int
分别是:图的代号
到达当前图经历了多少步
0所在的x坐标
0所在的y坐标
在以排列操作的时候,将二维转换为一维,转化后b~[x * 3 + y] = b[x][y]
- 1.初始化一个数组(6 ^ 7)为
0
,用来标记节点是否被探查过。- 2.将初始图转化成节点。
- 3.创建一个存储节点的队列。
- 4.将初始节点放入队列,并标记该节点探寻过,进入循环。
- 5.循环条件:队列不为空
- 5.1取出头节点,当前节点所代表的值是否等于11190(1 2 3 4 5 0 代表值),若等于,则直接返回该节点的步骤值;否则继续。
- 5.2判断是否能够左移,如果能够,则构造左移后的节点,放入队列。
- 5.3判断是否能够右移,如果能够,则构造右移后的节点,放入队列。
- 5.4判断是否能够上移,如果能够,则构造上移后的节点,放入队列。
- 5.5判断是否能够下移,如果能够,则构造下移后的节点,放入队列。
- 5.6将队列首踢出队列
- 6.若循环结束还没有返回值,说明无解,返回
-1
5.核心代码
5.1排列与代表值转换公式
排列到代表值
int now = board[0][0] * 7776 + board[0][1] * 1296 + board[0][2] * 216 + board[1][0] * 36 + board[1][1] * 6 + board[1][2] * 1;
代表值到排列
int temp = bfs.front()[0];
trans.push_back(temp / 7776);
temp %= 7776;
trans.push_back(temp / 1296);
temp %= 1296;
trans.push_back(temp / 216);
temp %= 216;
trans.push_back(temp / 36);
temp %= 36;
trans.push_back(temp / 6);
temp %= 6;
trans.push_back(temp);
5.2判断是否能够移动并添加值
//测试左移
if (y != 0) {
vector<int> left(4);
temp = trans[x * 3 + y];
trans[x * 3 + y] = trans[x * 3 + y - 1];
trans[x * 3 + y - 1] = temp;
int leftValue = trans[0] * 7776 + trans[1] * 1296 + trans[2] * 216 + trans[3] * 36 + trans[4] * 6 + trans[5] * 1;
if (!test[leftValue]) {
test[leftValue] = true;
left[0] = leftValue;
left[1] = step + 1;
left[2] = x;
left[3] = y - 1;
bfs.push(left);
}
//复原
trans[x * 3 + y - 1] = trans[x * 3 + y];
trans[x * 3 + y] = temp;
}
5.3初版结果
结果发现,我们的程序运行得很慢,还需要继续优化。
6.优化
6.1存储优化
首先,将存储了各个节点状态的数组由
int
数组变为bool
数组,将存储减少了32倍。
bool test[46656] = {false}; //用来判断图形是否被放入queue
6.2数据结构优化
将原先一维
vector<int>
存储的排列改为int
数组。
//还原数组
int trans[6]; //存储queue中的首元素
int temp = bfs.front()[0];
trans[0] = temp / 7776;
temp %= 7776;
trans[1] = temp / 1296;
temp %= 1296;
trans[2] = temp / 216;
temp %= 216;
trans[3] = temp / 36;
temp %= 36;
trans[4] = temp / 6;
temp %= 6;
trans[5] = temp;
6.3访问优化
通过
queue.front()
来访问首元素需要很多的复杂度,因此通过直接通过四次访问将首元素的值拷贝下来,下次直接使用这四个值,来节省访问队列的开销。
int step = bfs.front()[1];
int x = bfs.front()[2];
int y = bfs.front()[3];
7.最终结果
8.源代码
class Solution {
public:
int slidingPuzzle(vector<vector<int>>& board) {
//将图形转换为数字,每一幅图都转换成一个数字,公式为 第一位 * 6^6 + 第二位 * 6 ^ 5.......
//每个图像存储在一个vector<int> (4) 中,第一位是代号;第二位是当前走了多少步;第三位和第四位是 0 坐标
bool test[46656] = {false}; //用来判断图形是否被放入queue
int now = board[0][0] * 7776 + board[0][1] * 1296 + board[0][2] * 216 + board[1][0] * 36 + board[1][1] * 6 + board[1][2] * 1;
int end = 11190; //[1 2 3 4 5 0] 图形的数字代号
vector<int> nowValue(4); //构造首个
nowValue[0] = now;
nowValue[1] = 0;
for (int i = 0; i < 2; i++) { //定位 0 坐标
for (int j = 0; j < 3; j++) {
if (board[i][j] == 0) {
nowValue[2] = i;
nowValue[3] = j;
}
}
}
queue<vector<int> > bfs;
bfs.push(nowValue);
while (!bfs.empty()) { //广度优先搜索
if (bfs.front()[0] == end) { //达成
return bfs.front()[1];
} else {
//从代号得到图形,存储在trans中
int trans[6]; //存储queue中的首元素
int temp = bfs.front()[0];
trans[0] = temp / 7776;
temp %= 7776;
trans[1] = temp / 1296;
temp %= 1296;
trans[2] = temp / 216;
temp %= 216;
trans[3] = temp / 36;
temp %= 36;
trans[4] = temp / 6;
temp %= 6;
trans[5] = temp;
int step = bfs.front()[1];
int x = bfs.front()[2];
int y = bfs.front()[3];
//测试左移
if (y != 0) {
vector<int> left(4);
//左移变换
temp = trans[x * 3 + y];
trans[x * 3 + y] = trans[x * 3 + y - 1];
trans[x * 3 + y - 1] = temp;
//计算值
int leftValue = trans[0] * 7776 + trans[1] * 1296 + trans[2] * 216 + trans[3] * 36 + trans[4] * 6 + trans[5] * 1;
if (!test[leftValue]) {
test[leftValue] = true;
left[0] = leftValue;
left[1] = step + 1;
left[2] = x;
left[3] = y - 1;
bfs.push(left);
}
//复原
trans[x * 3 + y - 1] = trans[x * 3 + y];
trans[x * 3 + y] = temp;
}
//测试右移
if (y != 2) {
vector<int> right(4);
//右移变换
temp = trans[x * 3 + y];
trans[x * 3 + y] = trans[x * 3 + y + 1];
trans[x * 3 + y + 1] = temp;
//计算值
int rightValue = trans[0] * 7776 + trans[1] * 1296 + trans[2] * 216 + trans[3] * 36 + trans[4] * 6 + trans[5] * 1;
if (!test[rightValue]) {
test[rightValue] = true;
right[0] = rightValue;
right[1] = step + 1;
right[2] = x;
right[3] = y + 1;
bfs.push(right);
}
//复原
trans[x * 3 + y + 1] = trans[x * 3 + y];
trans[x * 3 + y] = temp;
}
//测试上移
if (x == 1) {
vector<int> up(4);
temp = trans[x * 3 + y];
trans[x * 3 + y] = trans[x * 3 - 3 + y];
trans[x * 3 - 3 + y] = temp;
int upValue = trans[0] * 7776 + trans[1] * 1296 + trans[2] * 216 + trans[3] * 36 + trans[4] * 6 + trans[5] * 1;
if (!test[upValue]) {
test[upValue] = true;
up[0] = upValue;
up[1] = step + 1;
up[2] = x - 1;
up[3] = y;
bfs.push(up);
} //复原
trans[x * 3 - 3 + y] = trans[x * 3 + y];
trans[x * 3 + y] = temp;
}
//测试下移
if (x == 0) {
vector<int> down(4);
temp = trans[x * 3 + y];
trans[x * 3 + y] = trans[x * 3 + 3 + y];
trans[x * 3 + 3 + y] = temp;
int downValue = trans[0] * 7776 + trans[1] * 1296 + trans[2] * 216 + trans[3] * 36 + trans[4] * 6 + trans[5] * 1;
if (!test[downValue]) {
test[downValue] = true;
down[0] = downValue;
down[1] = step + 1;
down[2] = x + 1;
down[3] = y;
bfs.push(down);
}
}
bfs.pop();
}
}
return -1;
}
};