题目:
是一道经典的八数码问题。将一个3×3的矩阵,通过每次和四周的数字进行互换,最终达到目标状态。其中用字母“l”表示相左移动,“u”表示向上,“r”表示向右,“d”表示向下,最后结果,输出移动的字母。
样例:
目标位置是 初始位置是:
为了从初始位置到达目标位置,需要先将x与5换位置,再与6换位置,也就是先往右移动再往下移动,故输出的值为 rd
如果谜题没有解决方案,您将打印到标准输出,或者单词“unsolvable”,或者一个完全由字母“r”、“l”、“u”和“d”组成的字符串,描述一系列产生解决方案的动作。该字符串不应包含空格并从行首开始。
思路:
首先可以看出是一道BFS(深度优先搜索的提目),我们用暴力法从x位置出发分别从左、上、右、下四个位置来移动,直到与目标状态相同。
思路虽然简单,但是仔细发现是会有重复的状态出现的,例如样例假如x向右移动后,下一次移动又向左移动了,就会发现回到初始位置了。如果不去掉这些重复的状态,程序会产生很无效的操作,复杂度大大增加。例如,总共的状态有9!=362880,再与新状态相对比,最多可产生9!×9!次检查。因此该问题最重要的是判重。
本题可以用数学方法“康托展开”来判重。通过康托展开将每个状态按照字典序排序并得到该状态的序号,例如012345678的序号就是1。通过其计算公式就可以实现判重的作用(具体原理和计算公式可以百度)
其中BFS用STL中的queue来实现。
代码编写:
为了便于运算我们在进行搜索时将题中x转换成了0,故在输入的时候需要将我们输入的字符数组转换成int型,如下代码段。这里还有一个细节需要注意的是,这里的输入用的cin.getline()函数而不是简单的cin>>形式输入,因为cin>>形式输入会自动过滤掉不可见字符(如空格 回车 tab等)这样的话将字符数组转化成int型就会出错,因为题目的输入是有空格的,所以这里要用cin.getline()函数(cin.getline()函数可以接收空格等)
int main()
{
char s[100];
cin.getline(s, 100);//输入
int pos = 0;
for (int i = 0; s[i] != '\0'; i++)//将char型转换为int型
{
if (s[i] == ' ') continue;
else if (s[i] == 'x') start[pos++] = 0;//将x转为0
else start[pos++] = s[i] - '0';
}
int num = bfs();
if (num == -1) printf("unsolvable\n");
return 0;
}
康托展开的代码:
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;
}
AC代码如下:
(需要用G++来运行,c++会出现编译错误,因为此题有多种解法,运用BFS+康托展开并不是最好的方法,但由于自己的水平不够,目前也只能用这种较为简单易懂的方法)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 362880;//状态共9!=362880
struct node
{
int state[9];//记录一个八数码的排列,即一个状态
int dis;//记录到起点的距离
string ans;
};
int dir[4][2] = { {-1,0},{0,-1},{1,0},{0,1} };//左、上、右、下顺时针方向,用于遍历
char turn[4] = { 'l','u','r','d' };//用字母表示左、上、右、下
int visited[maxn] = { 0 };//标记已走过的位置
int start[9];//起点
int goal[9] = { 1,2,3,4,5,6,7,8,0 };//目标
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;
}
bool check(int x, int y)//判断x,y是否越界
{
if (x >= 0 && x < 3 && y >= 0 && y < 3)
return true;
else return false;
}
int bfs()//深度优先搜索
{
node head;
memcpy(head.state, start, sizeof(head.state));//复制起点的状态
head.dis = 0;
queue<node>q;//用队列记入状态
Cantor(head.state, 9);//用康托展开判重,目的是对起点的visited[]赋初值
q.push(head);//将起点状态进队
while (!q.empty())//处理队列
{
head = q.front();
q.pop();
int z;
if (memcmp(head.state, goal, sizeof(goal)) == 0)//与目标状态对比
{
cout << head.ans << endl;//打印出题目的结果
return head.dis;
}
for (z = 0; z < 9; z++)//找到状态元素为0的位置,即为x的位置
{
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 + 3 * newy;//转化为一维
if (check(newx, newy))//如果每越界
{
node newnode = head;//复制head的状态,用来存新位置
swap(newnode.state[z], newnode.state[nz]); //0的交换
newnode.dis++;
if (Cantor(newnode.state, 9))//用康托展开判重
{
newnode.ans = head.ans + turn[i];//记录移动的位置,用字母表示
q.push(newnode);//将新的状态存入队列
}
}
}
}
return -1;
}
int main()
{
char s[100];
cin.getline(s, 100);//输入
int pos = 0;
for (int i = 0; s[i] != '\0'; i++)//将char型转换为int型
{
if (s[i] == ' ') continue;
else if (s[i] == 'x') start[pos++] = 0;//将x转为0
else start[pos++] = s[i] - '0';
}
int num = bfs();
if (num == -1) printf("unsolvable\n");
return 0;
}
大数据201 liyang