题目大意
8数码问题,在3x3的矩阵中填入0-8九个数字,0可以和它相邻的数字进行交换。从初始状态到达状态F(3x3的方格从上到下,从左到右,形成的数字串为123456780)所需要最少移动的次数。
题目分析
将3x3矩阵中的当前情形记为一个状态,用9个字符表示。然后根据方格0和它相邻的方格交换来进行状态的转移。可以采用广度优先的方式来进行搜索遍历,直到到达状态F,或者将所有的可能情况都遍历过来。
那么,如何判断一个状态是否被遍历过了呢?由于 用9个字符表示状态,每个字符有0-8 共9种可能,因此总共的情形为 9^9 = 387420489,空间复杂度太高,因此需要压缩: 由于9个方格中的数字均不相同,那么通过移动方格能够达到的合理的状态数就只有 9! =362880. 复杂度就可以接受了。因此需要实现将 0-8的一个排列定位到它在0-8全排列中的位置。
通过康托展开来实现:
将空间进行压缩之后,可以用广度优先搜索来解决,但是也可以通过A-star 算法来进行优化:
对每个状态都维护一个 F = G + H,表示该状态的代价。 G表示从初始状态到达该状态所花费的代价,H表示从该状态到达终点状态的估计代价。要求估计代价 H 一定小于等于 从该状态到达终点状态的实际代价!
将当前可进入的状态按照F进行排序,选择F最小的那个进行扩展。
H 可以通过估计函数来获得,本题中的估计函数就可以设置为 当前3x3矩阵中方格中的数字和最终状态对应方格中数字不同的方格的总数目。很容易证明 H 一定小于等于 实际从该状态到达终点状态的代价。
实现
#include<iostream>
#include<string.h>
#include<iostream>
#include<queue>
using namespace std;
char init_digit[3][3];
bool visited[362890];
struct Node{
char digit[3][3]; //状态记录
int g; //从起始状态到达该状态,已经花费的代价
int f; //该状态的价值, f = g + h(估价函数)
};
struct Cmp{
bool operator()(const Node& node1, const Node& node2){
return node1.f > node2.f;
}
};
void Init(){
memset(visited, false, sizeof(visited));
}
//康托展开法, 长度为n的排列 num[1,2....n],其在n的全排列中的序号为
//X = a[1]*(n-1)! + a[2]*(n-2)! +... +a[n]*0!;
//其中a[i] 表示在num[i+1, ...n]中比num[i]小的数量 /*逆康托展开
X = a[1]*(n-1)! + a[2]*(n-2)! + ... + a[n]*0!, a[i] 表示 num[i+1, ... n]中小于a[i]的数目
已知X,求出对应的 num[1,2...n]
可知, a[i]肯定 <= n - i. 则 a[i]*(n-i)! <= (n-i)*(n-i)!, 那么 a[i+1]*(n-i-1)! + a[i+2]*(n-i-2)! + ... a[n]*0!
<= (n-i-1)*(n - i - 1)! + (n-i-2)*(n - i - 2)! + .... 1*1! + 0*0! 由数学归纳法可以知道, 1*1! < 2!, 1*1! + 2*2! < 3!, .... 因此
(n-i-1)*(n - i - 1)! + (n-i-2)*(n - i - 2)! + .... 1*1! + 0*0! < (n-i)!
a[i+1]*(n-i-1)! + a[i+2]*(n-i-2)! + ... a[n]*0! < (n-i)! 因此, X / (n-1)! 的商c 即为 a[1], X / (n-1)! 的余数 r / (n-2)! 的商即为 a[2]....
*/ int digit2Int(char arr[3][3]){
// arr[0][0], arr[0][1] ... arr[2][2] 记为 num[1], num[2] ... num[9]
int result = 0, t = 1;
for (int i = 8; i >= 0; i--){
int row = i / 3, col = i % 3;
int k = 0;
for (int j = i + 1; j < 9; j ++)
k += (arr[j / 3][j % 3] < arr[row][col]);
result += k*t;
t *= (9 - i);
}
return result;
} int Estimate(char arr[3][3]){
int sum = 0;
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
if (3 * i + j == 8)
continue;
if (arr[i][j] != 3 * i + j + '1')
sum++;
}
}
return sum;
}
bool Succeed(char arr[3][3]){
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
int target = (3 * i + j + 1) % 9;
if (arr[i][j] != target + '0')
return false;
}
}
return true;
}
int move_step[4][2] = { { -1, 0 }, { 0, 1 }, { 1, 0 }, { 0, -1 } };
int Search(){
priority_queue<Node, vector<Node>, Cmp> pq;
//queue<Node> pq;
int X = digit2Int(init_digit);
visited[X] = true;
Node node; for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
node.digit[i][j] = init_digit[i][j];
}
}
node.g = 0;
node.f = 0 + Estimate(node.digit);
pq.push(node);
while (!pq.empty()){
//node = pq.front();
node = pq.top();
pq.pop();
if (Succeed(node.digit))
return node.g;
char cur_digit[3][3];
int cur_g = node.g;
int zero_r = 0, zero_c = 0;
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
cur_digit[i][j] = node.digit[i][j];
if (cur_digit[i][j] == '0'){
zero_r = i;
zero_c = j;
}
}
}
for (int i = 0; i < 4; i++){
int next_r = zero_r + move_step[i][0];
int next_c = zero_c + move_step[i][1];
if (next_r >= 0 && next_r <= 2 && next_c >= 0 && next_c <= 2){
memcpy(node.digit, cur_digit, sizeof(cur_digit));
node.digit[next_r][next_c] = '0';
node.digit[zero_r][zero_c] = cur_digit[next_r][next_c];
int X = digit2Int(node.digit);
if (visited[X])
continue;
visited[X] = true;
node.g = cur_g + 1;
node.f = node.g + Estimate(node.digit);
pq.push(node);
}
}
}
return -1;
}
int main(){
int T;
scanf("%d", &T);
while (T--){
for (int i = 0; i < 3; i++){
getchar();
scanf("%c %c %c", &init_digit[i][0], &init_digit[i][1], &init_digit[i][2]);
}
Init();
int step = Search();
if (step == -1)
printf("No Solution!\n");
else
printf("%d\n", step);
}
return 0;
}