困扰我多日的八数码问题终于解决了,一度对八数码问题不知道该如何下手,网上很多都是用A*算法解的,但是版本可以说各有千秋,自己一时间看看各个版本的代码,也弄的头昏脑涨的,这两天一直研究A*算法,然后想通过一个实例来好好学习下A*问题,这样如果能够很好的解决典型的8数码问题,对自己也有个很好的提升。在网上看到的版本大部分都是c++的,然后我想自己就通过java来实现,毕竟用java来实现算法要容易的多,同时也易于理解。
我想从以下几个方面来阐述解决8数码问题的方法
1 了解A*算法
2 A*算法的伪代码
3 如何在8数码问题中应用A*算法(即建立合适的启发式)
4 8数码问题比较特殊,需要判断是否有可行解
5 实现A*算法在8数码问题中的应用
1 了解A*算法
在网上看了很多文章,发现一篇感觉很好的阐述了A*算法的。连接如下:A*算法
1,从起始状态A开始,并且把它作为待处理点存入一个“开启列表”。开启列表就像一张购物清单。尽管现在列表里只有一个元素,但以后就会多起来。你的路径可能会通过它包含的其他状态,也可能不会。基本上,这是一个待检查方格的列表。
2,寻找起点周围通过上下左右移动所有可到达的状态。也把他们加入开启列表。为所有这些状态保存状态A作为“父方格”。当我们想描述路径的时候,父方格的资料是十分重要的。后面会解释它的具体用途。
3,从开启列表中删除状态A,把它加入到一个“关闭列表”。
4 取出开启列表中fvalue最低的状态,判断该状态性质
(1) 如果在开启列表中出现,那么判断该状态的gvalue通过当前节点(从open中取出,扩展出该节点的节点)作为父节点带来的gvalue 是否小于原先的父节点,若小于则需要更改它的父节点以及gvalue.
(2) 如果在关闭列表中出现,那么判断该状态的gvalue通过当前节点(从open中取出,扩展出该节点的节点)作为父节点带来的gvalue是否小于原先的父节,若小于则需要从关闭列表中删除该节点,并且将其加入开启列表,并更改其父节点为当前节点。
(3) 既不在开启列表,也不在关闭列表,这样就很简单,直接加入开启列表中就行(保持开启列表升序排列)
(4) 如果取出的节点为目标节点,则成功退出
2 A*算法的伪代码
创建两个表,OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。
算起点的估价值;
将起点放入OPEN表;
while(OPEN!=NULL)
{
从OPEN表中取估价值f最小的节点n;
if(n节点==目标节点){
break;
}
for(当前节点n 的每个子节点X)
{
算X的估价值;
if(X in OPEN)
{
if( X的估价值小于OPEN表的X估价值 ){
把n设置为X的父亲;
更新OPEN表中的估价值; //取最小路径的估价值
}
}
if(X inCLOSE) {
if( X的估价值小于CLOSE表的X估价值 ){
把n设置为X的父亲;
将该节点从close表中除去
把X节点放入OPEN //取最小路径的估价值
}
}
if(X not inboth){
把n设置为X的父亲;
求X的估价值;
并将X插入OPEN表中; //升序排列open
}
}//end for
将n节点插入CLOSE表中;
按照估价值将OPEN表中的节点排序; //实际上是比较OPEN表内节点f的大小,从最小路径的节点向下进行。
}//end while(OPEN!=NULL)
保存路径,即从终点开始,每个节点沿着父节点移动直至起点,这就是你的路径;
A*算法有个计算公式 f(x) = g(x)+h(x)
其中g(x)为从起始状态到当前状态所消耗的步数,h(x)为一个启发值,估算从当前状态到目标状态所需的步数,一般h(x)小于等于实际需要步数为好,这样不会将最优解忽略,因为h(x)和解空间有一些关系,如果h(x)设置的比实际需要的步数多,那么解空间就有可能将最优解忽略。举个例子吧,宽度优先搜索就是h(x)=0带来的效果,深度优先搜索就是g(x)=0带来的效果,不过h(x)距离h*(x)[实际需要的步数]的程度不能过大,否则h(x)就没有过强的区分能力,算法效率并不会很高。对一个好的h(x)的评价是:h(x)在h*(n)[实际需要的步数]的下界之下,并且尽量接近h*(n)[实际需要的步数].
那么8数码问题g(x) 为经过上下左右移动空格附近的数字来得到新状态所需步数,h(x)为当前状态与目标状态的距离,就是所有不在目标位置的数字总和,必然小于h*(x)
4 8数码问题比较特殊,需要判断是否有可行解
网上的一般解法,就是通过逆序数的奇排列偶排列判断,可以在这看到逆序数介绍
当目标状态的奇偶性质和初始状态的奇偶性质相同时,才能够进行转换,具体原理我不清楚,还需要查查。
5 实现A*算法在8数码问题中的应用
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;
public class astar {
public static void main(String args[])
{
int[][] startStatus={{2,3,1},{5,0,8},{4,6,7}}; //起始状态
int[][] endStatus = {{1,2,3},{4,5,6},{7,8,0}}; //结束状态
//int[][] endStatus = {{2,3,1},{5,8,7},{4,6,0}};
AstarDoer test = new AstarDoer(startStatus,endStatus);
test.run();
}
}
class AstarDoer
{
int[][] startStatus;
int[][] endStatus;
NodeComparator cmp = new NodeComparator();
PriorityQueue open = new PriorityQueue (1000000,cmp);
PriorityQueue close =new PriorityQueue (1000000,cmp);
public AstarDoer(int[][] startStatus,int[][] endStatus )
{
this.startStatus = new int[3][3];
this.endStatus = new int[3][3];
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
this.startStatus[i][j] = startStatus[i][j];
this.endStatus[i][j] = endStatus[i][j];
}
}
}
private int getReverse(int[][] status) //获得逆序数奇偶性
{
int reverse=0;
for(int i=0;i<9;i++)
{
for(int j=i+1;j<9;j++)
{
int k = i/3;
int m = i%3;
if(status[k][m]>status[j/3][j%3])
{
reverse+=1;
}
}
}
return reverse;
}
private boolean check(int[][] startStatus,int[][] endStatus)
{
//判断是否有可行解
int getS =0;
int getE =0;
getS = getReverse(startStatus);
getE = getReverse(endStatus);
if((getS%2) == (getE%2) )
{
return true;
}
return false;
}
private void initStart()
{
//将开始节点加入open列表
Node startNode = new Node(startStatus);
startNode.gvalue = 0;
startNode.parent = null;
startNode.hvalue = startNode.getH(endStatus);
startNode.fvalue = startNode.gvalue+startNode.hvalue;
open.add(startNode);
}
private boolean isInList(Node lNode,Node newNodeParent)
{
//判断该节点是否已经在之前出现过,之前出现的g值 肯定小于现在的g值,因为都是通过
//同一节点经过上下左右变换得到
while(newNodeParent != null)
{
if(lNode.equal(newNodeParent.status))
{
return true;
}
newNodeParent = newNodeParent.parent;
}
return false;
}
private void initChild(Node newNode,List newNodeChild )
{
//生成该节点周围的子节点
int whiteSpace = 0;
whiteSpace = newNode.getWhitespace();
/*得到空格位置判断左移产生节点*/
if((whiteSpace%3) !=2)
{
Node lNode = new Node(newNode.status);
lNode.status[whiteSpace/3][whiteSpace%3] = lNode.status[whiteSpace/3][(whiteSpace%3)+1];
lNode.status[whiteSpace/3][(whiteSpace%3)+1] = 0;
if(isInList(lNode,newNode.parent) == false)
{
lNode.parent = newNode;
lNode.gvalue = newNode.gvalue+1;
lNode.hvalue = lNode.getH(endStatus);
lNode.fvalue = lNode.gvalue+lNode.hvalue;
newNodeChild.add(lNode);
}
}
/*得到空格位置判断右移产生节点*/
if((whiteSpace%3) !=0)
{
Node lNode = new Node(newNode.status);
lNode.status[whiteSpace/3][whiteSpace%3] = lNode.status[whiteSpace/3][(whiteSpace%3)-1];
lNode.status[whiteSpace/3][(whiteSpace%3)-1] = 0;
if(isInList(lNode,newNode.parent) == false)
{
lNode.parent = newNode;
lNode.gvalue = newNode.gvalue+1;
lNode.hvalue = lNode.getH(endStatus);
lNode.fvalue = lNode.gvalue+lNode.hvalue;
newNodeChild.add(lNode);
}
}
/*得到空格位置判断上移产生节点*/
if((whiteSpace/3) !=2)
{
Node lNode = new Node(newNode.status);
lNode.status[whiteSpace/3][whiteSpace%3] = lNode.status[whiteSpace/3+1][whiteSpace%3];
lNode.status[whiteSpace/3+1][whiteSpace%3] = 0;
if(isInList(lNode,newNode.parent) == false)
{
lNode.parent = newNode;
lNode.gvalue = newNode.gvalue+1;
lNode.hvalue = lNode.getH(endStatus);
lNode.fvalue = lNode.gvalue+lNode.hvalue;
newNodeChild.add(lNode);
}
}
/*得到空格位置判断下移产生节点*/
if((whiteSpace/3) !=0)
{
Node lNode = new Node(newNode.status);
lNode.status[whiteSpace/3][whiteSpace%3] = lNode.status[whiteSpace/3-1][whiteSpace%3];
lNode.status[whiteSpace/3-1][whiteSpace%3] = 0;
if(isInList(lNode,newNode.parent) == false)
{
lNode.parent = newNode;
lNode.gvalue = newNode.gvalue+1;
lNode.hvalue = lNode.getH(endStatus);
lNode.fvalue = lNode.gvalue+lNode.hvalue;
newNodeChild.add(lNode);
}
}
}
private boolean isOPenOrCloseIn(Node newNode,PriorityQueue openList)
{
Iterator iter = openList.iterator();
while(iter.hasNext())
{
Node iterNode = iter.next();
if(iterNode.equal(newNode.status))
{
return true;
}
}
return false;
}
public void run()
{
if(check(startStatus,endStatus)== false )
{
System.out.println("can not change to that");
return;
}
initStart();
while(open.isEmpty()==false)
{
Node newNode = open.poll();
close.add(newNode);
if(newNode.equal(endStatus))
{
int i=0;
System.out.println("success finish this Task~~~~~");
while(newNode != null)
{
for(int j=0;j<9;j++)
{
System.out.print(newNode.status[j/3][j%3]);
System.out.print(" ");
if(j%3 == 2)
{
System.out.println();
}
}
System.out.println("step "+i+"status fvalue"+newNode.fvalue);
newNode = newNode.parent;
i+=1;
}
break;
}
List newNodeChild = new ArrayList();
initChild(newNode,newNodeChild);
for(Node iter:newNodeChild)
{
if(isOPenOrCloseIn(iter,open) == true)
{
Iterator iterNew = open.iterator();
while(iterNew.hasNext())
{
Node iterNode = iterNew.next();
if(iterNode.equal(iter.status))
{
if(iter.gvalue < iterNode.gvalue)
{
iterNode.parent = iter.parent;
iterNode.gvalue = iter.gvalue;
iterNode.fvalue = iter.fvalue;
}
}
}
}
else if(isOPenOrCloseIn(iter,close)==true)
{
Iterator iterNew = close.iterator();
while(iterNew.hasNext())
{
Node iterNode = iterNew.next();
if(iterNode.equal(iter.status))
{
if(iter.gvalue < iterNode.gvalue)
{
iterNode.parent = iter.parent;
iterNode.gvalue = iter.gvalue;
iterNode.fvalue = iter.fvalue;
close.remove(iterNode);
open.add(iter);
}
}
}
}
else
{
open.add(iter);
}
}
}
}
class NodeComparator implements Comparator {
@Override
public int compare(Node x, Node y) {
return x.fvalue - y.fvalue;
}
}
}
class Node
{
Node parent;
public int[][] status = new int[3][3];
int fvalue;
int gvalue;
int hvalue;
public Node(int[][] status)
{
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
this.status[i][j] = status[i][j];
}
}
}
public void setG(int gvalue)
{
this.gvalue = gvalue;
}
public int getWhitespace()
{
int k=0;
for(int i=0;i<9;i++)
{
if(status[i/3][i%3] == 0)
{
k=i;
return k;
}
}
return k;
}
public boolean equal(int[][] endStatus)
{
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
if(status[i][j]!= endStatus[i][j])
{
return false;
}
}
}
return true;
}
public int getH(int[][] endStatus)
{
int k=0;
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
if(status[i][j] != endStatus[i][j])
{
k+=1;
}
}
}
return k;
}
}