搜索—八数码问题与康托判重
八数码难题
题目描述
在 \(3\times 3\) 的棋盘上,摆有八个棋子,每个棋子上标有 \(1\) 至 \(8\) 的某一数字。棋盘中留有一个空格,空格用 \(0\) 来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为 \(123804765\)),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
输入格式
输入初始状态,一行九个数字,空格用 \(0\) 表示。
输出格式
只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数。保证测试数据中无特殊无法到达目标状态数据。
样例 #1
样例输入 #1
283104765
样例输出 #1
4
提示
样例解释

图中标有 \(0\) 的是空格。绿色格子是空格所在位置,橙色格子是下一步可以移动到空格的位置。如图所示,用四步可以达到目标状态。
并且可以证明,不存在更优的策略。
分析
把一个棋局看成一个状态图,总共有9!=362880个状态。从初始棋局开始.每次移动转到下一个状态,到达目标棋局后停止。
八数码问题是一个经典的BFS问题。八数码从初始状态出发.每次转移都逐步逼近目标状态。每转移一次.步数加一,当到达目标时,经过的步数就是最短路径
下图是样例的转移过程。该图中起点为(A.0).A表示状态,即{123084765}这
个棋局:0是距离起点的步数。从初始状态A出发,移动数字0到邻居位置,按左、上、右、下的顺时针顺序,有3个转移状态B 、C、 D:目标状态是F.停止

用队列描述这个BFS过程:
(1)A进队,当前队列是{A};
(2) A出队.A的邻居B.C.D进队,当前队列是{B.C.D}.步数为1:
(3)B出队,E进队,当前队列是{C.D.E}.E的步数为2;
(4) C出队.转移到F.检验F是目标状态,停止,输出F的步数2。
仔细分析上述过程.发现从B状态出发实际上有Y、X两个转移方向.而X正好是初始状态A,重复了。同理Y状态也是重复的。如果不去掉这些重复的状态,程序会产生很多无效操作,复杂度会很大,所以关键是判重。使用暴力法最大对比9!*9!,不行。
借助数学方法——康托展开可以快速判重


第1行是0~8这9个数字的全排列,共9!-362880个,按从小到大排序。第2行是每个排列对应的位置,例如最小的{012345678}在第0个位置,最大的(876543210}在最后的362880-1这个位置。
函数Cantor()实现的功能是:输人一个排列.即第1行的某个排列.计算出它的Cantor值.即第2行对应的数
Cantor()的复杂度为O(n2),n是集合中元素的个数。在本题中.完成搜索和判重的总复杂度是O(n!2).远比用暴力判重的总复杂度O(n!n!)小.有了这个函数.八数码的程序能很快判重:每转移到一个新状态,就用Cantor()判断这个状态是否被处理过,如果处理过,则不转移。
下图是判断2143是{1,2,3,4}的全排列中第几大的数

上述过程的反过程是康托逆展开:某个集合的全排列.输人一个数字k.返回第k大的排列
代码实现
#include <bits/stdc++.h>
using namespace std;
const int LEN = 362880;
struct node {
int state[9];
int dis;
};
int dir[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
int visited [LEN] = {0};
int start[9] = {1 ,2 ,3 ,8 ,0 ,4 ,7 ,6 ,5};
int goal[9];
long int factory[] = {1,1,2,6,24,120,720,5040,40320,362880};
bool Cantor(int str[],int n){
long result = 0;
for(int i = 0;i < n;i++){
int counted = 0;
for(int j = i+1;j < n;j++){
if(str[i]>str[j])
++counted;
}
result += counted * factory[n-i-1];
}
if(!visited[result]){
visited[result] = 1;
return 1;
}
else
return 0;
}
int bfs(){
node head;
memcpy(head.state,start,sizeof(head.state));
head.dis = 0;
queue<node>q;
Cantor(head.state,9);
q.push(head);
while(!q.empty()){
head = q.front();
q.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){
node newnode;
memcpy(&newnode,&head,sizeof(struct node));
swap(newnode.state[z],newnode.state[nz]);
newnode.dis++;
if(memcmp(newnode.state,goal,sizeof(goal))==0)
return newnode.dis;
if(Cantor(newnode.state,9))
q.push(newnode);
}
}
}
return -1;
}
int main()
{
string s;cin>>s;
for(int i = 0;i < 9;i++){
goal[i] = s[i] - '0';
}
if(memcmp(&start,goal,sizeof(goal))==0)cout<<0<<endl;
else{
int num = bfs();
cout<<num<<endl;
}
return 0;
}

浙公网安备 33010602011771号