小游戏系列算法之五广度优先搜索,双向广搜,八数码,华容道

来源:互联网 发布:mac os 10.12镜像下载 编辑:程序博客网 时间:2024/05/30 13:41

前段时间在玩仙五前,遇上了蚩尤冢拼图这个小游戏。

其实就是八数码问题,一直想着如何才能用最少步数求解,于是就写了个程序。

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 += (8 - spaceNum) * getJieCheng(8);return hashNum;}


 

这样,我们已经可以完成对八数码的求解。
它的流程为:
1.把开始结点压入队。
2.把开始结点状态写入哈希表
3.出队,访问结点。
4.创建结点的子结点,检查是否为目标状态。若是,搜索结束,若否,检查哈希表是否存在此状态。若已有此状态,跳过,若无,把此结点压入队。
重复3,4步骤,即可得解。
最后,我们根据目标状态结点回溯其父节点,可以得到完整的路径。

Q4:空间搜索状态过多,花费过长,如何优化?
A4:广度优先搜索的弊端,由于是盲目搜索,当目标所在的层度比较深时,会产生过多的搜索集,这需要花费很多的储存空间,并且花费大量时间。为此,解决方法是减少搜索的结点。由于目标状态已知,因此可以使用双向广度优先搜索。

 

实现双向广搜的方法只需在初始操作压入初始结点的时候,压入目标结点,这样出队的时候就会交替搜索。

当然还需要记录当前搜索的方向,我是把它标记在结点类里面的。

 

public class Node {private var arr:Array;//记录八数码状态private var pNode:Node;//记录父节点private var direction:uint;//记录操作方向,一边回溯操作private var status:uint;//记录当前搜索状态,是正向还是逆向


最后为喜闻乐见的渣渣代码。
AS3 双向广搜 八数码

 

Main.as

package {import flash.display.Sprite;import flash.events.Event;import org.Node;import org.Queue;import org.BFS;/** * ... * @author fengsser */public class Main extends Sprite {public function Main():void {if (stage) init();else addEventListener(Event.ADDED_TO_STAGE, init);}private function init(e:Event = null):void {removeEventListener(Event.ADDED_TO_STAGE, init);var arr:Array = [1, 2, 3, 4, 5, 6, 7, 8, 0];//var arr2:Array = [4, 1, 3, 2, 8, 5, 7, 0, 6];var arr3:Array = [3, 8, 4, 1, 0, 2, 7, 5, 6];//var arr4:Array = [2, 1, 3, 4, 0, 6, 7, 8, 5];//var arr5:Array = [1,2,3,4,5,6,7,0,8];var startNode:Node = new Node();//初始状态var endNode:Node = new Node();//目标状态var bfs:BFS;endNode.setArr(arr);//randomSort(arr);startNode.setArr(arr3);bfs = new BFS(startNode, endNode);bfs.start();}private function randomSort(arr:Array):void{              var copyArr:Array = arr.slice();              var l:uint = arr.length;              while(l){                  arr[l-1]=copyArr.splice(int(Math.random()*l--),1)[0];              }             }}}


 

Node.as

 

package org {/** * ... * @author fengsser */public class Node {private var arr:Array;//记录八数码状态private var pNode:Node;//记录父节点private var direction:uint;//记录操作方向,一边回溯操作private var status:uint;//记录当前搜索状态,是正向还是逆向public function Node() {arr = null;pNode = null;}public function setArr(arr:Array):void {this.arr = arr;}public function setPNode(node:Node):void {this.pNode = node;}public function getArr():Array {return this.arr;}public function getPNode():Node {return this.pNode;}public function setDir(dir:uint):void {this.direction = dir;}public function getDir():uint {return this.direction;}public function setStatus(status:uint):void {this.status = status;}public function getStatus():uint {return this.status;}}}


 

Queue.as

package org {/** * ... * @author fengsser */public class Queue {private var arr:Array;private var front:uint = 1;private var end:uint;private var size:uint = 10000;private var count:uint = 0;public function Queue() {this.arr = [];}public function push(node:*):void {if (front % size == end) {trace("queue is full");return;}if (front == size) {front = 0}count++;arr[front++] = node;}public function pop():* {if ((end+1)%size == front )return null;count--;end++;if (end == size) {end = 0;}return arr[end];}public function getCount():uint {return count;}public function show():void {for (var i:uint = end; i < front-1; i++ ) {printfNode(arr[i+1].getArr());trace(i);}}private function printfNode(arr:Array):void {for (var i:uint = 0; i < arr.length; i += 3 ) {trace(arr[i]+","+arr[i+1]+","+arr[i+2]);}trace("");}}}


 

BFS.as

 

package org {import flash.events.TimerEvent;import flash.utils.Timer;import org.Node;import org.Queue;import flash.utils.getTimer;/** * ... * @author fengsser */public class BFS {private var queue:Queue;//队private var nodeArr:Array;private var endNode:Node;private var resultArr:Array;public var time:int = 0;private var closedArr:Array;//哈希表记录访问状态private var endHashNum:uint;private var UP:uint = 0;private var DOWN:uint = 1;private var LEFT:uint = 2;private var RIGHT:uint = 3;private var statusFront:uint = 1;private var statusEnd:uint = 2;public function BFS(startNode:Node,endNode:Node) {queue = new Queue();nodeArr = [];resultArr = [];this.endNode = endNode;closedArr = [];startNode.setStatus(statusFront);endNode.setStatus(statusEnd);queue.push(startNode); //初始结点入队queue.push(endNode);//目标结点入队,以双广closedArr[getHashNum(startNode.getArr())] = startNode;//哈希表记录初始状态endHashNum = getHashNum(endNode.getArr());}public function start():void {var flag:Boolean = true;//判断搜索是否已经结束var status:uint;while (queue.getCount() > 0 && flag ) {var node:Node = queue.pop();//出队status = node.getStatus();//可能的产生的四个子节点,上下左右for (var i:int = 0; i < 4; i++ ) {var tempArr:Array = node.getArr();if ((tempArr=Move(tempArr,i))) {var tempNode:Node = new Node();tempNode.setArr(tempArr);tempNode.setPNode(node);tempNode.setDir(i);tempNode.setStatus(status);var tempHashNum:uint = getHashNum(tempNode.getArr());if (closedArr[tempHashNum]) {//如果已访问过此状态if(Node(closedArr[tempHashNum]).getStatus() == status){//如果访问方向一致,则跳过tempNode = null;continue;}else {//否则,交汇,输出交汇结点flag = false;if (status == statusFront) {resultArr.push(tempNode);resultArr.push(closedArr[tempHashNum]);}else {resultArr.push(closedArr[tempHashNum]);resultArr.push(tempNode);}break;}}closedArr[tempHashNum] = tempNode;//哈希表记录状态queue.push(tempNode);//待访问结点入队}}}if (!flag) {trace("found result");//回溯路径if (resultArr.length > 1) {var bufferArr:Array = new Array();bufferArr.push(resultArr[1]);while (bufferArr[bufferArr.length - 1].getPNode() != null) {bufferArr.push(bufferArr[bufferArr.length - 1].getPNode());}bufferArr.reverse();bufferArr[bufferArr.length - 1] = resultArr[0];while (bufferArr[bufferArr.length - 1].getPNode() != null) {bufferArr.push(bufferArr[bufferArr.length - 1].getPNode());}resultArr = null;resultArr = bufferArr;}else {while (resultArr[resultArr.length - 1].getPNode() != null) {resultArr.push(resultArr[resultArr.length - 1].getPNode());}}for (i = resultArr.length - 1; i >= 0; i-- ) {printfNode(resultArr[i].getArr());}}else {trace("not found");}trace(getTimer());}private function printfNode(arr:Array):void {for (var i:uint = 0; i < arr.length; i += 3 ) {trace(arr[i]+","+arr[i+1]+","+arr[i+2]);}trace("");}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 += (8 - spaceNum) * getJieCheng(8);return hashNum;}//获取阶乘private function getJieCheng(num:uint):uint {if(num>1)return num * getJieCheng(num - 1);else {return num;}}private function Move(arr:Array,dir:uint):* {var zeroNode:uint = arr.indexOf(0);var zeroRow:uint = zeroNode / 3;var zeroColumn:uint = zeroNode % 3;var nextNode:uint;switch(dir) {case UP:zeroRow -= 1;break;case DOWN:zeroRow += 1;break;case LEFT:zeroColumn -= 1;break;case RIGHT:zeroColumn += 1;break;}if (zeroColumn > 2 || zeroColumn <0 || zeroRow > 2 || zeroRow <0) return null;arr = arr.slice();nextNode = zeroRow * 3 + zeroColumn;arr[zeroNode] = arr[nextNode];arr[nextNode] = 0;return arr;}}}


 测试结果:

found result
3,8,4
1,0,2
7,5,6

3,0,4
1,8,2
7,5,6

0,3,4
1,8,2
7,5,6

1,3,4
0,8,2
7,5,6

1,3,4
7,8,2
0,5,6

1,3,4
7,8,2
5,0,6

1,3,4
7,0,2
5,8,6

1,3,4
7,2,0
5,8,6

1,3,0
7,2,4
5,8,6

1,0,3
7,2,4
5,8,6

1,2,3
7,0,4
5,8,6

1,2,3
7,4,0
5,8,6

1,2,3
7,4,6
5,8,0

1,2,3
7,4,6
5,0,8

1,2,3
7,4,6
0,5,8

1,2,3
0,4,6
7,5,8

1,2,3
4,0,6
7,5,8

1,2,3
4,5,6
7,0,8

1,2,3
4,5,6
7,8,0

 


 

原创粉丝点击