智能算法实验——八数码问题
八数码游戏实验
一. 问题简介
八数码游戏是经典的益智游戏。九宫排字问题(又称八数码问题)是人工智能当中有名的难题之一。问题是在 3 X 3 方格盘上放有八个数码,剩下第九个为空,每一空格其上下左右的数字可移动至空格。
问题给定初始位置和目标位置,要求通过一些列的数码移动,将初始位置转化为目标位置。
二. 前置知识
康托展开
康托展开的定义
康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。
其中, 为整数,表示原数的第
位在当前未出现元素中是排在第几个,并且有
。
Code
int cantor(int a[], int n) {
int ans = 0, num = 0;
for(int i = 1; i <= n; i++) {
for(int j = i + 1; j <= n; j++) {
if(a[j] < a[i]) {
num++;
}
}
ans += num * factorial[n - i]; // 阶乘
num = 0;
}
return ans;
} 逆康托展开
同理,给定一个排列的序号,可以通过逆康拓得到该排列。例如,给出一到五的排列(1,2,3,4,5),想要得到全排列中排位第61的排列,通过逆康托展开可以得到排列组合为 34152。具体过程如下:
(1) 用 61/ 4! = 2余13,说明a[5]=2,说明比首位小的数有2个,所以首位为3。
(2) 用 13 / 3! = 2余1,说明a[4]=2,说明在第二位之后小于第二位的数有2个,所以第二位为4。
(3) 用 1 / 2! = 0余1,说明a[3]=0,说明在第三位之后没有小于第三位的数,所以第三位为1。
(4) 用 1 / 1! = 1余0,说明a[2]=1,说明在第二位之后小于第四位的数有1个,所以第四位为5。
(5) 最后一位补上剩余的数字2。
通过以上分析,所求排列组合为 34152。
Code
void decantor(int x, int n) {
vector<int> v, a;
for(int i = 1; i <= n; i++) {
v.emplace_back(i);
}
for(int i = n; i >= 1; i--) {
int r = x % factorial[i - 1];
int t = x / factorial[i - 1];
x = r;
sort(v.begin(), v.end());
a.emplace_back(v[t]);
v.erase(v.begin() + t);
}
} 启发式搜索
启发式搜索算法, 即A*算法,读音为A-star。
启发式搜索就是在状态空间中的搜索,首先对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无谓的搜索路径,提高了效率。在启发式搜索中,对位置的估价是十分重要的。采用了不同的估价可以有不同的效果。
启发中的估价是用估价函数表示的,如:
其中 是节点
的估价函数,
是在状态空间中从初始节点到
节点的实际代价,
是从
到目标节点最佳路径的估计代价。在这里主要是
体现了搜索的启发信息,因为
是已知的。如果说详细点,
代表了搜索的广度的优先趋势。但是当
时,可以省略
,而提高效率。
启发函数设定
令, 其中, 为
当前节点的步数,
为当前数码与目标数码的曼哈顿距离之和。
算法设计
- 将初始数码压入优先队列。
- 取出此时堆顶优先级最高的元素。
- 扩展子节点,向上下左右四个方向移动空格,生成对应子节点。
- 判断当前子节点是否为最终状态,如果是最终状态则结束搜索,否则执行5。
- 通过哈希判断是否到达过该节点,如果该状态尚未到达,则放入优先队列。
- 跳转到步骤2。
八数码问题Code(Astar + 康托展开)
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 3.7e5;
const int mod = 998244353;
int pre[N], v[N], factorial[20], a[20], Destination_Cantor;
int dist[4][2] = {-1, 0, 1, 0, 0, -1, 0, 1};
bool vis[N];
pair<int, int> point[50];
char table[5] = "udlr";
struct node {
int maze[3][3];
int x, y; // 记录x的位置
int f, g, h; // A*的估价函数
int flag;
bool operator < (const node &s) const {
return f > s.f;
}
}start, tail;
int Cantor(node tmp) { // 康托展开
int tot = 0;
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
a[++tot] = tmp.maze[i][j];
}
}
int ans = 0, num = 0;
for(int i = 1; i <= tot; i++) {
for(int j = i + 1; j <= tot; j++) {
if(a[j] < a[i]) {
num++;
}
}
ans += num * factorial[tot - i]; // 阶乘
num = 0;
}
return ans + 1;
}
bool check(node tmp) {
int ar[15] = {0};
int tot = 0;
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
ar[++tot] = tmp.maze[i][j];
}
}
int sum = 0;
for(int i = 1; i <= tot; i++) {
for(int j = 1; j < i; j++) {
if(ar[j] == 'x' || ar[i] == 'x') continue;
if(ar[j] > ar[i]) {
sum++;
}
}
}
if(sum & 1) return false;
return true;
}
void Init() {
factorial[0] = 1;
for(int i = 1; i <= 9; i++) {
factorial[i] = factorial[i - 1] * i;
}
node tmp;
int tot = 0;
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
tmp.maze[i][j] = ++tot;
}
}
tmp.maze[2][2] = 'x';
Destination_Cantor = Cantor(tmp);
int l = 0, r = 0;
for(int i = 1; i <= 9; i++) {
point[i % 9].first = l;
point[i % 9].second = r;
r++;
if(r == 3) {
l++, r = 0;
}
}
}
int cal_H(node tmp) {
int sum = 0;
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
int now = tmp.maze[i][j];
if(now == 'x') now = 0;
sum += abs(point[now].first - i) + abs(point[now].second - j);
}
}
return sum;
}
void print(node tmp) {
string ans;
int sum = Destination_Cantor;
while(pre[sum] != -1) {
ans += table[v[sum]];
sum = pre[sum];
}
reverse(ans.begin(), ans.end());
cout << ans << '\n';
}
void Astar(node now) {
priority_queue<node> q;
memset(vis, 0, sizeof(vis));
memset(pre, -1, sizeof(pre));
memset(v, -1, sizeof(v));
now.h = cal_H(now), now.g = 0, now.flag = -1, now.f = now.g + 2 * now.h;
q.push(now);
vis[Cantor(now)] = true;
while(!q.empty()) {
auto tmp = q.top(); q.pop();
if(Cantor(tmp) == Destination_Cantor) {
print(tmp);
return ;
}
for(int i = 0; i < 4; i++) {
tail.x = tmp.x + dist[i][0];
tail.y = tmp.y + dist[i][1];
int x = tmp.x, y = tmp.y;
if(tail.x < 0 || tail.x >= 3 || tail.y < 0 || tail.y >= 3) continue;
for(int j = 0; j < 3; j++) {
for(int k = 0; k < 3; k++) {
tail.maze[j][k] = tmp.maze[j][k];
}
}
swap(tail.maze[x][y], tail.maze[tail.x][tail.y]);
int cur = Cantor(tail);
if(!check(tail)) {
continue;
}
if(!vis[cur]) {
vis[cur] = true;
tail.g = tmp.g + 1;
tail.h = cal_H(tail);
tail.f = tail.g + 2 * tail.h;
if(tail.x == x + 1) tail.flag = 1; // 向下
else if(tail.x == x - 1) tail.flag = 2; // 向上
else if(tail.y == y + 1) tail.flag = 3; // 向右
else if(tail.y == y - 1) tail.flag = 4; // 向左
pre[cur] = Cantor(tmp);
v[cur] = i;
q.push(tail);
}
}
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
string str;
Init();
while(getline(cin, str)) {
int r = 0, c = 0;
for(int i = 0; i < str.size(); i++) {
if(isdigit(str[i])) {
start.maze[r][c] = str[i] - '0';
c++;
if(c == 3) {
c = 0, r++;
}
} else if(str[i] == 'x') {
start.maze[r][c] = str[i];
start.x = r, start.y = c;
c++;
if(c == 3) {
c = 0, r++;
}
}
}
int now = Cantor(start);
if(now == Destination_Cantor) {
cout << '\n'; continue;
}
if(!check(start)) {
cout << "unsolvable\n";
continue;
}
Astar(start);
}
return 0;
}
// 1234567x8
/*
1 2 3
4 5 6
7 x 8
*/ 算法课的作业



查看7道真题和解析