题目描述
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
输入格式:
输入初始状态,一行九个数字,空格用0表示
输出格式:
只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)
输入样例#1:
283104765
输出样例#1:
4
对于这种结果的深度不是很大, 而可以搜得很大的题用A*启发式搜索来解决, 洛谷上给出了31个测试点, 必须疯狂剪枝
剪枝:
1,排除在上和下左和右之间的来回移动
2. 计算还未移动到目标位置的个数加上已经移动的次数, 若大于设定的深度k则此路不通
#include<iostream>
using namespace std;
int ans[3][3] = { 1, 2, 3, 8, 0, 4,7, 6, 5 };
int a[3][3];
int k;
int net[4][2] = {{1, 0}, {0, 1}, {0, -1}, {-1, 0} }; /上与下, 左与右下标和为3,
//用于避免来回, 很重要, 否则超时
bool ok() //检查是否到达目标
{
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
if (a[i][j] != ans[i][j])
return false;
return true;
}
bool test(int sum) //实现剪枝2
{
int cnt = 0;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
if (a[i][j] != ans[i][j])
cnt++;
if (cnt + sum > k)
return false;
return true;
}
bool solve(int sum, int x, int y, int pre) //sum已走步数, pre上次移动方向下标
{
if (sum == k)
{
if (ok()) return true;
return false;
}
for (int i = 0; i < 4; i++)
{
int na = x + net[i][0];
int nb = y + net[i][1];
if (na >= 0 && na < 3 && nb >= 0 && nb < 3 && pre + i != 3) //pre + i 实现剪枝1
{
swap(a[na][nb], a[x][y]);
if (test(sum))
if (solve(sum + 1, na, nb, i))
return true;
swap(a[na][nb], a[x][y]);
}
}
return false;
}
int main()
{
int x, y;
string s;
cin >> s;
ios::sync_with_stdio(false);
int p = 0,q = 0;
for (int i = 0; i < s.length(); i++)
{
int num = s[i] - '0';
a[p][q++] = num;
if (!num) x = p, y = q - 1;
if (q == 3)
p++, q = 0;
}
if (ok()) //特判, 给出的即是目标要求的
{
cout << "0" << endl;
return 0;
}
while (++k)
{
if (solve(0, x, y, -1))
{
cout << k << endl;
return 0;
}
}
}```