简单搜索&&进阶搜索 - Virtual Judge (vjudge.net)
【题目描述】
在一个3×3的九宫格上,填有1~8八个数字,空余一个位置,例如下图:
1 | 2 | 3 |
4 | 5 | 6 |
7 | 8 |
在上图中,由于右下角位置是空的,你可以移动数字,比如可以将数字6下移一位:
1 | 2 | 3 | 1 | 2 | 3 | |
4 | 5 | 6 | → | 4 | 5 | |
7 | 8 | 7 | 8 | 6 |
或者将数字8右移一位:
1 | 2 | 3 | 1 | 2 | 3 | |
4 | 5 | 6 | → | 4 | 5 | 6 |
7 | 8 | 7 | 8 |
1~8按顺序排列的情况称为“初始状态”(如最上方图)。“八数码问题”即是求解对于任意的布局,将其移动至“初始状态”的方法。
给定一个现有的九宫格布局,请输出将它移动至初始状态的移动方法的步骤。
【输入】
输入包含多组数据,处理至文件结束。每组数据占一行,包含8个数字和表示空位的‘x’,各项以空格分隔,表示给定的九宫格布局。
例如,对于九宫格
1 | 2 | 3 |
4 | 6 | |
7 | 5 | 8 |
输入应为:1 2 3 x 4 6 7 5 8
注意,输入的数字之间可能有(不止一个?)空格。
【输出】
对于每组输入数据,输出一行,即移动的步骤。向上、下、左、右移动分别用字母u、d、l、r表示;如果给定的布局无法移动至“初始 状态”,请输出unsolvable。
如果有效的移动步骤有多种,输出任意即可。
对于网格形式的图,有以下这些启发函数可以使用:
如果图形中只允许朝上下左右四个方向移动,则可以使用曼哈顿距离(Manhattan distance)。
如果图形中允许朝八个方向移动,则可以使用对角距离。
如果图形中允许朝任何方向移动,则可以使用欧几里得距离(Euclidean distance)。
该题使用曼哈顿距离。
解题思路:
对于这个题目,直接用 bfs 会时间超限,那么可以用 A* 简化它,然后对于判重问题,用 book 数组存储康托展开的值解决,看是否有相同状态。
对于 bfs 的搜索,在数据较为复杂的情况,会有很多无效状态进行广搜,而广搜后面又接着广搜,会降低效率。
A* 的主要作用是:粗略计算出当前的状态到目标状态至少需要走多少步,把无效的点去除。
因为是粗略估计,主要是如果很精确的话,这个时间复杂度也不小,况且还可以直接得到答案,所以 A* 是一个 O(n) 复杂度,不会影响效率。
A* 实现的思路是:f(x)= g(x)+h(x),其中 g(x)是到达该点已经进行的操作数,h(x)是粗略估计到达目标状态的操作数(该数只会比实际操作数大,后面会解释),f(x)是两者之和。计算 h(x)时,是判断它直接到目标的距离(曼哈顿距离),将计算的 f(x)存入open list ,然后从 open list 中选择 f(x) 最小的节点放入closelist中,并将它视作新的父节点,按照以上步骤类推,不断的重复,一直到搜索到终点节点,完成路径搜索。
其中 open list 为待考察节点,close list 为已考察节点,在判断 f(x)的最小值并加入父结点后,其余未用到的结点放入 close list。
康托展开:
首先说说为什么用这个,对应一个迷宫地图来说,判断这个点有没有走过,直接用一个 book 数组记录就好了,但是对于数字地图,这种方法就不太适合。
对于这个题,它存储的是数据,把 3*3 的矩阵转换为一个一维数组(输入的时候实现),然后对于这些排列,就相当于全排列,而康托展开是统计全排列的总数(例如:1 的全排列是 1 种,2 的全排列是 2 种,3 的全排列是 6 种…n 的全排列是 n!种)。每次进行操作后,数组的排列方式发生改变时,它的在排列种对应的康托值也会变化。康托值是其逆序数*对应位置。
对于 1,2,3,4,5 来说,它的康托展开值为 0*4!+0*3!+0*2!+0*1!=0;
对于 4,3,1,5,2 来说,3 * 4!+2 * 3!+0 * 2!+1 * 1!+0 * 0!=85.
首先要明确一点的是,在没有解决方案的时候,也就是逆序对为奇数,此时用 A* 算法的效率和 bfs 的效率一样,所以在判断逆序对后,有解决方案的时候再使用 A* 算法。
步骤:
#include
#include
#include
#include
using namespace std;
struct node
{
int a[3][3];//存当前状态
int c, x, y;//康托值、行数、列数
int f, g, h;
//重载运算符(定义小于号)
bool operator < (const node& F) const
{
//h相等则按g的大小 从小到大排序
if (h == F.h)
return g > F.g;
//否则按h的大小 从小到大排序
return h > F.h;
}
};
struct node k;
int fact[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 };//康拓进制
int f[362880];//存操作下标
int path[362880];//存操作步骤
//康拓函数
int cantor(int t[][3])
{
int x[9];
for (int i = 0; i < 9; i++)
x[i] = t[i / 3][i % 3];
int ans = 0;
for (int i = 0; i <= 8; i++)
{
int cnt = 0;
for (int j = i + 1; j <= 8; j++)
if (x[i] > x[j])
cnt++;
ans += cnt * fact[8 - i];
}
return ans;
}
//曼哈顿
int geth(int t[][3])
{
int ans = 0;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
if (t[i][j]==0)
continue;
int x = (t[i][j] - 1) / 3;
int y = (t[i][j] - 1) % 3;
ans += abs(x - i) + abs(y - j);
}
return ans;
}
//打印答案
void print(int x)
{
if (k.c != x)
{
print(f[x]);
if (path[x] == 1) cout << "r";
if (path[x] == 2) cout << "d";
if (path[x] == 3) cout << "l";
if (path[x] == 4) cout << "u";
}
}
int mubiao[][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 0 };
void A()
{
priority_queue q;
bool vis[372880] = { 0 };//标记
int dx[] = { 0, 0, 1, 0, -1 };
int dy[] = { 0, 1, 0, -1, 0 };
//每个数的康拓值
int target = cantor(mubiao);
k.c = cantor(k.a);
vis[k.c] = true;
q.push(k);
while (!q.empty())
{
struct node st = q.top();
q.pop();
if (target == st.c)//到达目标状态
{
print(st.c);
printf("\n");
return;
}
for (int i = 1; i <= 4; i++)
{
struct node ed = st;
ed.x = ed.x + dx[i];
ed.y = ed.y+dy[i];
if (0 <= ed.x && ed.x < 3 && 0 <= ed.y && ed.y < 3)
{
swap(ed.a[ed.x][ed.y], ed.a[st.x][st.y]);
ed.c = cantor(ed.a);
if (!vis[ed.c])
{
vis[ed.c] = true;
ed.g++; ed.h = geth(ed.a); ed.f = ed.g + ed.h;
f[ed.c] = st.c;
path[ed.c] = i;
q.push(ed);
}
}
}
}
}
int main()
{
char ch;
while (cin<> ch;
if (ch == 'x')
k.x = i / 3, k.y = i % 3, k.a[i / 3][i % 3] = 0;
else
k.a[i / 3][i % 3] = ch - '0';
}
//逆序对
int seq[9];
for (int i = 0; i < 9; i++)
{
seq[i] = k.a[i / 3][i % 3];
}
int ans = 0;
for (int i = 0; i < 9; i++)
{
for (int j = i + 1; j < 9; j++)
{
if (seq[i] == 0 || seq[j] == 0)
continue;
if (seq[i] > seq[j])
ans++;
}
}
if (ans%2==1)
printf("unsolvable\n");
else
{
//预估价值
k.g = 0;
k.h = geth(k.a);
k.f = k.g + k.h;
A();
}
}
return 0;
}