前段时间在玩仙五前,遇上了蚩尤冢拼图这个小游戏。
其实就是八数码问题,一直想着如何才能用最少步数求解,于是就写了个程序。
Q1:什么是八数码问题?
A1:首先假定一个3*3的棋盘(如上图),分别有1,2,3……8 共计8个不同的棋子放在上面,棋盘上还残留一个空格。移动棋子的规则是:与空格横竖相邻的棋子可以移动到空格的地方。现给定棋盘一个初始状态,一个目标状态,问如何通过移动棋子让初始状态变成目标状态。
为方便表达,一个棋盘的状态可以表示成这样,0表示空格:
123
456
780
在程序中,可以用一个以为数组储存以上状态:[1,2,3,4,5,6,7,8,0]
设0的在数组中的位置为zeroPos
那么空格在期盼中的实际位置为
zeroRow = zeroPos / 3;
zeroColumn = zeroPos % 3;
那么怎么求解八数码问题呢?最先想到的也是最简单的方法便是使用广度优先搜索,因为它实行起来简单,并且得到的解一定是最优解。
Q2:什么是广度优先搜索
A2:BFS,全称是Breadth First Search,是一种盲目搜索,按字面理解就是先向横向搜索,如树的话就是先访问同级层的结点,再访问下一层。
如图:节点的顺序表示访问次序。
Q3:那么如何用广度优先搜索求解八数码问题呢?
A3:
其实八数码的每个状态就相当于树的一个结点。因为空各子有可能向上下左右四个方向移动,因此每个结点都有可能有四个子结点。
如图:
在实际开发中,我们可以用“队”的储存结构来储存将要访问的结点,这样可以刚好达到广度优先搜索的顺序。
我们还要主要到,在空格的移动中,有可能会出现相同的状态,因此我们需要记录访问过的状态。如果检测到某个状态是访问过的,就不再将它加入“队”里了。
对于已访问状态的检索,这里推荐用哈希表。因为对于一个**{1,2,3,4,...,n},它的全排列对应的康托展开的值是唯一的。
把一个整数X展开成如下形式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[2]*1!+a[1]*0! 其中,a为整数,并且0<=a[i]<i(1<=i<=n)。这个X就是康托展开的值也是哈希的键值。
对于空格,取其(9-位置)再乘以8!。
例如:
1 3 7 2 4 6 8 0 5 的哈希的键值等于:
0*0! + 0*1! + 0*2! + 2*3! + 1*4! + 1*5! + 0*6! + 3*7! + (9-8)*8! = 55596
0*0!,0*1!,其实就是第n位数的逆序数*相应的阶乘值。
private function getHashNum(arr:Array):uint {
var spaceNum:uint = arr.indexOf(0);
var copyArr:Array = arr.slice();
copyArr.splice(spaceNum, 1);
var hashNum:uint;
for (var i:uint=0; i < copyArr.length; i++ ) {
var backNum:uint=0;
for (var j:uint=0; j < i; j++ ) {
if (copyArr[j] > copyArr[i]) {
//求解逆序数
backNum++;
}
}
hashNum += backNum * getJieCheng(i);
}
hashNum +