终于,在学习了完深搜和广搜、Dijkstra、二叉堆和优先队列以及A*算法之后,可以看一下IDA*算法了。因为求解4x4的拼图游戏所要搜素的节点数很大,所以应该采用IDA*算法,而3x3的拼图游戏则可以采用A*算法。
IDA*算法是A*的一个变形,和A*算法不同的是他不保存之前的搜索状态(意味着同一个节点可能会被搜索多次),它的搜索效率会稍微低于A*算法。笔者对于IDA*的翻译是迭代深度的A*算法,它是一种有信息提示的搜索(informed search),但其实现思想是基于无信息提示的IDDFS(iterative deepening depth-first search)。
下面先来讨论IDDFS,再来实现基于IDDFS的IDA*。
打开维基百科,发现没有中文版的介绍。于是笔者只能用咱的蹩脚英语翻译一下了:
<译>Iterative deepening depth-first search (IDDFS)是一个反复调用depth-limited search的状态空间搜索策略,每次迭代都会增加深度限制直到d(d为深度最浅的可行解对应的深度)。IDDFS等同于广度优先搜索,但是它需要更少的内存开销。每次迭代它会以深度优先搜索的顺序来访问节点,但是总体的累积效果还是广度优先搜索的。</译>
好了,有点乱哈,一下BFS一下DFS的。咱还是不翻译了,这么说吧,我们可以把一次深度优先搜索看做一个深度为n的搜索单元,这个搜索单元是基于上级的广度优先搜索的。然后该深度n会在每次迭代是不断加深,直到找到可行解为止。因为深度优先是不存储"备选节点“的,于是整个IDDFS的内存开销不大,又因为搜索的上层控制是广度优先搜索,于是整个IDDFS等同于广度优先搜索。IDDFS集合了DFS和BFS两者的优点,其空间复杂度是O(bd),时间复杂度是O(b^d)。感兴趣的朋友可以查看笔者的另一篇文章:IDDFS(Iterative deepening depth-first search)的Java实现
现在来说一下IDA*,设想有这样的一个场景,有老板A和员工B,老板A有一个拼图游戏需要求解,老板观察了一下问题,根据问题的manhattan距离得到了一个可能的最小步骤解n。于是把员工B叫过来,说你给我在n步之内解出题目。于是员工B开始解题,他的方法很简单采用深搜的方法搜索所有符合该manhattan距离限制的解,此时,可能当员工B遍历了所以的结果后还是没有找到可行解,于是他只能把这个结果和老板A说了,老板A看了以后,允许解题步骤限制+1,然后员工B在以这个n+1的步骤限制,从头开始做一个遍历,可能他还没有找到可行解,他就会再去找老板,老板说再加1吧,如此循环,直到员工B求得可行解。
实现代码:
package com.wly.algorithmbase.search;
/**
* IDA*求解15puzzle问题
* IDA*整合了IDDFS和A*算法。其中IDDFS控制了求解过程中的内存开销,A*算法意味着"启发式"搜索。
* IDA*可以理解成迭代深度的A*算法,其中这里的"深度"指的是问题的解的"假定损耗"。
* 使"假定损耗"不断迭代增加,检验是否能在"假定损耗"的前提找到可行解,如果不行的话,就继续迭代。
* 这里和A*算法不同的是没有开放列表,由于采用了IDDFS的策略,IDA*是深度优先搜索的,故此没有开放列表。
* @author wly
* @date 2013-12-20
*
*/
public class IDAStarAlgorithm {
//分别代表左、上、右、下四个移动方向的操作数
private int[] up = {-1,0};
private int[] down = {1,0};
private int[] left = {0,-1};
private int[] right = {0,1};
/**注意,这里UP和DOWN,LEFT和RIGHT必须是两两相对的,因为后面代码中使用
* ((dPrev != dCurr) && (dPrev%2 == dCurr%2))
* 来判断前后两个移动方向是否相反
*/
private final int UP = 0;
private final int DOWN = 2;
private final int LEFT = 1;
private final int RIGHT = 3;
private int SIZE;
//各个目标点坐标
private int[][] targetPoints;
//用于记录移动步骤,存储0,1,2,3,对应上,下,左,右
private static int[] moves = new int[100000];
private static long ans = 0;; //当前迭代的"设想代价"
//目标状态
private static int[][] tState = {
{1 ,2 ,3 ,4 } ,
{5 ,6 ,7 ,8 } ,
{9 ,10,11,12} ,
{13,14,15,0 }
};
private static int[][] sState = {
{2 ,10 ,3 ,4 } ,
{1 ,0,6 ,8 } ,
{5 ,14,7,11} ,
{9,13,15,12 }
};
//初始状态
// private static int[][] sState = {
// {12,1 ,10,2 } ,
// {7 ,11,4 ,14} ,
// {5 ,0 ,9 ,15} ,
// {8 ,13,6 ,3}
// };
private static int blank_row,blank_column;
public IDAStarAlgorithm(int[][] state) {
SIZE = state.length;
targetPoints = new int[SIZE * SIZE][2];
this.sState = state;
//得到空格坐标
for(int i=0;i<state.length;i++) {
for(int j=0;j<state[i].length;j++) {
if(state[i][j] == 0) {
blank_row = i;
blank_column = j;
break;
}
}
}
//得到目标点坐标数组
for(int i=0;i<state.length;i++) {
for(int j=0;j<state.length;j++) {
targetPoints[tState[i][j]][0] = i; //行信息
targetPoints[tState[i][j]][1] = j; //列信息
}
}
}
/**
* 讨论问题的可解性
* @param state 状态
*/
private boolean canSolve(int[][] state) {
if(state.length % 2 == 1) { //问题宽度为奇数
return (getInversions(state) % 2 == 0);
} else { //问题宽度为偶数
if((state.length - blank_row) % 2 == 1) { //从底往上数,空格位于奇数行
return (getInversions(state) % 2 == 0);
} else { //从底往上数,空位位于偶数行
return (getInversions(state) % 2 == 1);
}
}
}
public static void main(String[] args) {
IDAStarAlgorithm idaAlgorithm = new IDAStarAlgorithm(sState);
if(idaAlgorithm.canSolve(sState)) {
System.out.println("--问题可解,开始求解--");
//以曼哈顿距离为初始最小代价数
int j = idaAlgorithm.getHeuristic(sState);
System.out.println("初始manhattan距离:" + j);
int i = -1;//置空默认移动方向
long time = System.currentTimeMillis();
//迭代加深"最小代价数"
for(ans=j;;ans++) {
if(idaAlgorithm.solve(sState
,blank_row,blank_column,0,i,j)) {
break;
}
}
System.out.println("求解用时:"+(System.currentTimeMillis() - time));
idaAlgorithm.printMatrix(sState);
int[][] matrix = idaAlgorithm.move(sState,moves[0]);
for(int k=1;k<ans;k++) {
matrix = idaAlgorithm.move(matrix, moves[k]);
}
} else {
System.out.println("--抱歉!输入的问题无可行解--");
}
}
public int[][] move(int[][]state,int direction) {
int row = 0;
int column = 0;
for(int i=0;i<state.length;i++) {
for(int j=0;j<state.length;j++) {
if(state[i][j] == 0) {
row = i;
column = j;
}
}
}
switch(direction) {
case UP:
state[row][column] = state[row-1][column];
state[row-1][column] = 0;
break;
case DOWN:
state[row][column] = state[row+1][column];
state[row+1][column] = 0;
break;
case LEFT:
state[row][column] = state[row][column-1];
state[row][column-1] = 0;
break;
case RIGHT:
state[row][column] = state[row][column+1];
state[row][column+1] = 0;
break;
}
printMatrix(state);
return state;
}
public void printMatrix(int[][] matrix) {
System.out.println("------------");
for(int i=0;i<matrix.length;i++) {
for(int j=0;j<matrix.length;j++) {
System.out.print(matrix[i][j] + " ");
}
System.out.println();
}
}
/**
* 求解方法
* @param state 当前状态
* @param blank_row 空位的行坐标
* @param blank_column 空格的列坐标
* @param dep 当前深度
* @param d 上一次移动的方向
* @param h 当前状态估价函数
* @return
*/
public boolean solve(int[][] state,int blank_row,int blank_column,
int dep,long d,long h) {
long h1;
//和目标矩阵比较,看是否相同,如果相同则表示问题已解
boolean isSolved = true;
for(int i=0;i<SIZE;i++) {
for(int j=0;j<SIZE;j++) {
if(state[i][j] != tState[i][j]) {
isSolved = false;
}
}
}
if(isSolved) {
return true;
}
if(dep == ans) {
return false;
}
//用于表示"空格"移动后的坐标位置
int blank_row1 = blank_row;
int blank_column1 = blank_column;
int[][] state2 = new int[SIZE][SIZE];
for(int direction=0;direction<4;direction++) {
for(int i=0;i<state.length;i++) {
for(int j=0;j<state.length;j++) {
state2[i][j] = state[i][j];
}
}
//本地移动方向和上次移动方向刚好相反,跳过这种情况的讨论
if(direction != d && (d%2 == direction%2)) {
continue;
}
if(direction == UP) {
blank_row1 = blank_row + up[0];
blank_column1 = blank_column + up[1];
} else if(direction == DOWN) {
blank_row1 = blank_row + down[0];
blank_column1 = blank_column + down[1];
} else if(direction == LEFT) {
blank_row1 = blank_row + left[0];
blank_column1 = blank_column + left[1];
} else {
blank_row1 = blank_row + right[0];
blank_column1 = blank_column + right[1];
}
//边界检查
if(blank_column1 < 0 || blank_column1 == SIZE
|| blank_row1 < 0 || blank_row1 == SIZE) {
continue ;
}
//交换空格位置和当前移动位置对应的单元格
state2[blank_row][blank_column] = state2[blank_row1][blank_column1];
state2[blank_row1][blank_column1] = 0;
//查看当前空格是否正在靠近目标点
if(direction == DOWN && blank_row1
> targetPoints[state[blank_row1][blank_column1]][0]) {
h1 = h - 1;
} else if(direction == UP && blank_row1
< targetPoints[state[blank_row1][blank_column1]][0]){
h1 = h - 1;
} else if(direction == RIGHT && blank_column1
> targetPoints[state[blank_row1][blank_column1]][1]) {
h1 = h - 1;
} else if(direction == LEFT && blank_column1
< targetPoints[state[blank_row1][blank_column1]][1]) {
h1 = h - 1;
} else {
//这种情况发生在任意可能的移动方向都会使得估价函数值变大
h1 = h + 1;
}
if(h1+dep+1>ans) { //剪枝
continue;
}
moves[dep] = direction;
//迭代深度求解
if(solve(state2, blank_row1, blank_column1, dep+1, direction, h1)) {
return true;
}
}
return false;
}
/**
* 得到估价函数值
*/
public int getHeuristic(int[][] state) {
int heuristic = 0;
for(int i=0;i<state.length;i++) {
for(int j=0;j<state[i].length;j++) {
if(state[i][j] != 0) {
heuristic = heuristic +
Math.abs(targetPoints[state[i][j]][0] - i)
+ Math.abs(targetPoints[state[i][j]][1] - j);
}
}
}
return heuristic;
}
/**
* 计算问题的"倒置变量和"
* @param state
*/
private int getInversions(int[][] state) {
int inversion = 0;
int temp = 0;
for(int i=0;i<state.length;i++) {
for(int j=0;j<state[i].length;j++) {
int index = i* state.length + j + 1;
while(index < (state.length * state.length)) {
if(state[index/state.length][index%state.length] != 0
&& state[index/state.length]
[index%state.length] < state[i][j]) {
temp ++;
}
index ++;
}
inversion = temp + inversion;
temp = 0;
}
}
return inversion;
}
}
运行结果:
--问题可解,开始求解--
初始manhattan距离:12
求解用时:0
------------
2 10 3 4
1 0 6 8
5 14 7 11
9 13 15 12
------------
2 0 3 4
1 10 6 8
5 14 7 11
9 13 15 12
------------
0 2 3 4
1 10 6 8
5 14 7 11
9 13 15 12
------------
1 2 3 4
0 10 6 8
5 14 7 11
9 13 15 12
------------
1 2 3 4
5 10 6 8
0 14 7 11
9 13 15 12
------------
1 2 3 4
5 10 6 8
9 14 7 11
0 13 15 12
------------
1 2 3 4
5 10 6 8
9 14 7 11
13 0 15 12
------------
1 2 3 4
5 10 6 8
9 0 7 11
13 14 15 12
------------
1 2 3 4
5 0 6 8
9 10 7 11
13 14 15 12
------------
1 2 3 4
5 6 0 8
9 10 7 11
13 14 15 12
------------
1 2 3 4
5 6 7 8
9 10 0 11
13 14 15 12
------------
1 2 3 4
5 6 7 8
9 10 11 0
13 14 15 12
------------
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 0
O啦~~~
转载请保留出处:http://blog.csdn.net/u011638883/article/details/17287721
谢谢!!
参考文档: