问题的简单描述
- 3×3九宫棋盘,放置数码为1 -8的8个棋牌,剩下一个空格,只能通过棋牌向空格的移动来改变棋盘的布局。
要求:根据给定初始布局(即初始状态)和目标布局(即目标状态),如何移动棋牌才能从初始布局到达目标布局,找到合法的走步序列。
功能设计
可计算这两个有序数列的逆序值,如果两者都是偶数或奇数,则可通过变换到达,否则,这两个状态不可达。这样,就可以在具体解决问题之前判断出问题是否可解,从而可以避免不必要的搜索。
-
本次使用广度优先搜索方案解决该问题,保存每次走完之后的棋盘数组以及由上一步到达这一步所经历的步骤(即空格的上下左右移动方向),空格的约束是不能移出棋盘
变量设计
-
首先是每个节点的设计
struct node { // 表示一个九宫格 int xy[3][3]; //dir的 0 1 2 3分别代表上一步是如何移动得来:左 上 右 下 int dir; };
-
接着是其余所需变量
// 表示最多可以走的步数,可以随意设置数组最大值 struct node step[102], end; // 表示当前搜索的次数 int count = 1;
算法设计
- 首先将 step[0] 赋值,然后设置一个最大的步数,然后将目标节点位置初始化到这个 node 中。也就是给 step[101] 赋值。
- 然后经过对 0 的上下左右移动来判断是否达到目标值(注意的是如果0是由上到下移动得来,则下一步就不能够再向上移动,否则会与父节点冲突),如果走完了所有的步数都还没能找到,则传递找不到信息
- 如果在这过程中找到了目标值(判断目标值是否相等的方法使用了一个 long long 数字来将九宫格中每个数字 * 10的 n次幂 然后相加得到一个唯一的值),则打印出目标值并且程序结束。
关键代码
-
首先是找出 0 的位置
//找出0的位置,参数 num 表示的是第几次的移动 int loction(int num) { int I; for (i = 0; i < 9; I++) // 返回的是 0 在 9 宫格中的位置数字 0 ~ 8 if (step[num].xy[i / 3][i % 3] == 0) return I; printf("未能找到 0 位置"); return -1; }
-
对每一步操作后的九宫格数组进行唯一性标记
long long sign(int num) { // 这种标记采取了比较笨的方法,直接由每个格子里面的值相加生成了一个数字来表示坐标的唯一性 long long sum; sum = step[num].xy[0][0]*100000000 + step[num].xy[0][1]*10000000 + step[num].xy[0][2]*1000000 + step[num].xy[1][0]*100000 + step[num].xy[1][1]*10000 + step[num].xy[1][2]*1000 + step[num].xy[2][0]*100 + step[num].xy[2][1]*10 + step[num].xy[2][2]; return sum; }
-
对 0 数字进行上下左右移动,此时需要判定四个边界
void move(int num) { int loc; // 首先确定 0 的位置 loc = loction(num); // 记录这个九宫格已经走过的方向,也就是它是由父节点由哪个方向移动得来 int stand = step[num].dir; // 判断是否可以继续向上并且上一步不是将 0 向上移动 if (loc / 3 != 0 && stand != UP) { // 首先将当前的值赋给当前 num 步的下一步 step[count] = step[num]; // 交换 0 和 0 下方数字的位置 step[count].xy[loc / 3][loc % 3] = step[count].xy[loc / 3 - 1][loc % 3]; step[count].xy[loc / 3 - 1][loc % 3] = 0; step[count].dir = DOWN; count++; } // 判断下边界并且上一步的移动不是已经向下了,否则又会移动回和父节点一样的九宫格 if (loc / 3 != 2 && stand != DOWN) { step[count] = step[num]; // 将当前 0 与下边的位置的数字交换 step[count].xy[loc / 3][loc % 3] = step[count].xy[loc / 3 + 1][loc % 3]; // 将下边的数字换到原 0 位置处 step[count].xy[loc / 3 + 1][loc % 3] = 0; // 方向改为向上 step[count].dir = UP; count++; } // 判断左边界 if (loc % 3 != 0 && stand != LEFT) { step[count] = step[num]; // 将当前 0 位置与左边数字的位置交换 step[count].xy[loc / 3][loc % 3] = step[count].xy[loc / 3][loc % 3 - 1]; step[count].xy[loc / 3][loc % 3 - 1] = 0; step[count].dir = RIGHT; count++; } // 判断右边界 if (loc % 3 != 2 && stand != RIGHT) { step[count] = step[num]; step[count].xy[loc / 3][loc % 3] = step[count].xy[loc / 3][loc % 3 + 1]; step[count].xy[loc / 3][loc % 3 + 1] = 0; step[count].dir = LEFT; count++; } }