中级实训第三阶段提出了如何用java,将打乱的拼图恢复到正确的位置的问题。并提示采用两种方法:
- 广度搜索
- 启发式算法
广度优先算法
广度优先算法属于盲目搜索算法,基本思想是将拼图移动的每种情况看成一个节点,从起始节点(拼图的起始状态)开始依次访问未访问过的邻接节点,直到寻找到结果节点(即拼图到达正确位置)时结束。这里不再赘述。
启发式算法
这部分只要求完成一个估价函数,来估计每个节点的重要性。不同于盲目搜索,启发式算法会选择最有价值的节点来作为下一步移动,因此比广度优先算法要更加快捷。
首先是对于估价函数的确定
根据题目要求,n*n 拼图的状态由 长度为n+1 数组表示,第0号位置为空节点的位置,接下来的n位为内容从1到n*n的块的位置。
如:
因此,确定三个估价函数:
- 当前块的位置与正确位置的曼哈顿距离
- 当前块的位置与正确位置的欧式距离
- 后续不正确节点数
容易知道,第i号元素的位置为:
- 表示行
- 表示列
需要特别注意的是,x的计算结果表示行,因此计算行距时是拼图垂直方向的距离。同理,y表示列距,为拼图水平方向的距离
假设块当前在第i位置,正确位置为j
则曼哈顿距离和欧式距离分别为:
因此,我将估价函数设为这三个函数的线性组合:
由函数可以知道,每个节点的total值越小,代表其价值更高。
调整参数a,b
确定参数可以有很多种算法。曾经尝试过使用SOR方法来迭代,使得完成一次拼图之后访问的所有节点数最少。显然,这里我将完成拼图所需要访问的所有节点当作cost函数值,但是cost的值必须在每次搜索完全部节点才能获得,因此无法写出明确的函数表达式。其次,由于参数值必须为整数int,而SOR要求所有参数和为1。因此这种方法没法实施。
所以最终采用了笨办法,通过随机产生参数值并进行多次ASearch测试,计算平均访问节点数,假如结果比上一次检验的结果好,则将当前参数替换旧参数。否则保留旧参数。
另外,在ASearch过程,由于每个节点都要调用一次估价函数,因此我直接为jigsaw类添加了两个变量paraA, paraB。
调参函数
public void changePar() {
// TODO Auto-generated method stub
int n = 10;
int totalInit = 20000;
while (n != 0) {
int initA = paraA;
int initB = paraB;
int time = 4; //表示每次检验进行搜索的次数
this.paraA = (int)(Math.random()*10);
this.paraB = (int)(Math.random()*10);
int totalNum = 0;
for (int i = 0; i < time; i ++) {
searchedNodesNum = 0;
JigsawNode dest = new JigsawNode(new int[]{25,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,0});
JigsawNode start = Solution.scatter(dest, 1000);
ASearch(start, dest);
totalNum += searchedNodesNum;
}
System.out.printf("average: %d\n", totalNum/time);
if(totalNum/time > totalInit) {
paraA = initA;
paraB = initB;
}
else {
totalInit = totalNum/time;
}
n--;
System.out.printf("current: %d\n", totalInit);
System.out.printf("para1: %d, para2: %d \n", paraA, paraB);
}
}
估价函数
public void estimateValue(JigsawNode jNode, int a, int b) {
int s = 0; // 后续节点不正确的数码个数
int dimension = JigsawNode.getDimension();
for(int index =1 ; index<dimension*dimension; index++){
if(jNode.getNodesState()[index]+1!=jNode.getNodesState()[index+1])
s++;
}
int wrongPosition = 0;
int lineDis = 0;
int cirDis = 0;
for(int i = 1; i <= dimension*dimension; i++){
for(int j = 1; j <= dimension*dimension; j++ ){
if(jNode.getNodesState()[i] != 0 && jNode.getNodesState()[i] == endJNode.getNodesState()[j]){
wrongPosition++;
int x = Math.abs(((i-1)/dimension - (j-1)/dimension));
int y = Math.abs(((i-1)%dimension - (j-1)%dimension));
cirDis += ( x + y ); //x 行数 , y列数
lineDis += Math.sqrt( x*x + y*y);
//cirDis曼哈顿距离,lineDis欧式距离
break;
}
}
}
//int total = 2*cirDis+lineDis+s;
int total = a*cirDis+b*s+(10-a-b)*lineDis;
jNode.setEstimatedValue(total);
}
由于每次搜索需要较长时间,因此每次检验的迭代数设的较小,导致结果没有很好,只是作说明用。并且由于不能改动已有函数,因此每次迭代都会产生冗长的输出,此处只截取部分输出。