八数码问题
八数码问题和状态图搜索
八数码问题
BFS我们很熟悉,一般来说在leetcode上多是value类型,其实其处理对象也可以是状态。下面一起来看看八数码问题:
在一个3x3的棋盘上放置1~8的8个方块,每个占一个格,另外剩余一个空闲的格。与空格相邻的数字方块可以移动到空格中。
任务
任务1:指定初始棋局和目标棋局,计算出最少的移动步数;
任务2:输出数码的移动序列;
把空格看成0,一共9个数字。
输入样例:
1 2 3 0 8 4 7 6 5
1 0 3 8 2 4 7 6 5
输出样例:
2
[解析:]
这里有两个巧妙的地方,一个是将棋局看作一个状态图,那么总共有
个状态,从初始棋盘开始,每次移动到下一个状态,到达目标棋局后终止;
八数码问题是一个经典的BFS问题,从起始位置开始由内向外扩散开来,从而逼近目标,当到达目标时就是步数最短路径。其实BFS也用在权值一样的求最短路径问题中,当然用图也可以搞定,这就是后话了。
回到本题来,在进行BFS遍历时会遍历许多重复的状态,如果不去重那么程序会产生很多无效操作,另外一个巧妙的地方使用快速判重方式。如果在这里采用暴力去重,那么需要将新状态与个状态比较,会产生次判断,明显不行。
康托去重
这里介绍一个数学方法:康托展开(Cantor Expansion)来去重;
说白了,Cantor去重实际是一个特殊hash函数:
状态 | 012345678 | 012345687 | 012345768 | ... | 876543210 |
---|---|---|---|---|---|
Cantor | 0 | 1 | 2 | ... | 362880-1 |
Cantor()的复杂度为O(),n是集合中元素的个数,因此本题中完成搜索和判重的总复杂度为O(),远比暴力解O()小。
下面介绍康托的原理:
例子:判断2143是{1, 2, 3, 4}中全排列中第几大数;
计算在2143前面的排列数目,可转换为一下排列和:
1)首位小于2的排列:这里比2小的就只有1,因此后面3位排列为;可写成
2)首位为2,第二位小于1的数列:没有,亦可写成
3)前两位为21,第三位小于4的所有排列:只有3一个,
4)前3位位214,第4位小于3的所有排列:无,写为
把一个集合产生的全排列按照字典序排序,第X个排列的计算公式为:
其中a[i]表示原数的第i位在当前未出现的元素中排在第几个(从0开始),且有
上面去重是Cantor的逆展开:某个集合的全排列,输入数字K,返回第K大的排列;
#include <bits/stdc++.h>
const int LEN = 362880; //总状态数9!=362880;
using namespace std;
struct node
{
int state[9]; //记录八数码的一个状态;
int dis; //记录到起点的距离;
};
int dir[4][2] = {
{-1, 0},
{1, 0},
{0, -1},
{0, 1},
};
int visited[LEN] = {0};
int start[9]; //起始状态;
int target[9]; //目标状态;
//阶乘计算结果预存;
long int factory[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
bool Cantor(int str[], int n)
{
long res = 0;
for (int i = 0; i < n; i++)
{
int counter = 0;
for (int j = i + 1; j < n; j++)
{
if (str[j] < str[i])
counter++;
}
res += counter * factory[n - i - 1];
}
if (!visited[res])
{
visited[res] = 1;
return true;
}
return false;
}
int bfs()
{
struct node head;
memcpy(head.state, start, sizeof(head.state));
head.dis = 0;
queue<node> qu;
Cantor(head.state, 9);
qu.push(node);
while (!qu.empty())
{
head = qu.front();
qu.pop();
int z;
for (z = 0; z < 9; z++)
{
if (head.state[z] == 0)
break;
}
int x = z % 3;
int y = z / 3;
for (int i = 0; i < 4; i++)
{
int newx = x + dir[i][0];
int newy = y + dir[i][1];
int nz = newx + newy * 3;
if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3)
{
struct node newnode;
memcpy(&newnode, &head, sizeof(struct node));
swap(newnode.state[z], newnode.state[nz]); //交换0的位置;
newnode.dis++;
if (memcmp(newnode.state, target, sizeof(target)) == 0)
{
return newnode.dis;
}
if (Cantor(newnode.state, 9))
qu.push(newnode);
}
}
}
return -1;
}
int main()
{
for (int i = 0; i < 9; i++)
cin >> start[i];
for (int i = 0; i < 9; i++)
cin >> target[i];
int num = bfs();
if (num != -1)
cout << num << endl;
else
cout << "Impossible" << endl;
return 0;
}
下面是OJ的两题,可以切一下
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效