八数码问题及其扩展
先来介绍一下八数码问题
游戏的棋盘被分割成3x3的区域,上面放着标记有1~8八个数字的方形棋子,剩下一个区域为空。
游戏过程中,只能移动棋子到相邻的空区域上。当小Ho将8个棋子都移动到如下图所示的位置时,游戏就结束了。
现在的问题在于如何判断初始状态能否到达目标状态?
为了方便,我们把它写成一维的字符串,用 0 代替空格
302581647-->123456780
首先我们引入逆序的概念
设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同。
如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。
求出除0之外所有数字的逆序数之和,也就是每个数字前面比它大的数字的个数的和,称为这个状态的逆序。
当空格在同一行中左右移动的时候,该状态的逆序并不会变。
当空格在不同行中上下移动的时候,该数就前移了两位或者后移了两位,这样就有三种情况,两数均比它大或小,则逆序减二或加二;两数一大一小,则逆序不变。
所以空格的操作并不会改变序列逆序的奇偶性,所以只要初始状态和目标状态逆序的奇偶性一致就有解。
目标状态逆序为 0
302581647 的逆序为 10, 所以有解!
bool check(){ int s[20]; int cnt = 0; for(int i = 0; i<3; i++){ for(int j = 0; j<3; j++){ s[3*i+j] = start.map[i][j]; if(s[3*i+j] == 'x') continue; for(int k = 3*i+j-1; k>=0; k--){ if(s[k] == 'x') continue; if(s[k]>s[3*i+j]) cnt++; } } } if(cnt%2) return false; return true; }
下面我们把八数码问题扩展一下,不是 3X3 的方格,而是 NXN 的方格
同理可知,
当空格在同一行中左右移动的时候,该状态的逆序并不会变。
当空格在不同行中上下移动的时候,该数就前移了N-1位或者后移了N-1位。
所以,当 N 为奇数的时候,逆序的奇偶性不会改变,所以只要初始状态和目标状态的奇偶性一致就有解。
而当N 为偶数的时候,因为每上下移动一次,逆序的奇偶性就改变一次,所以要先算出空格到它的目标位置需要的行数m,
然后判断如果初始状态的逆序数加上m与目标状态的逆序数奇偶性相同,则有解;否则无解!
也试着想过NXNXN 的三维是否有解,原理应该是差不多的(逃