终于,在学习了完深搜和广搜、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
谢谢!!
参考文档:
http://en.wikipedia.org/wiki/IDA*
http://heuristicswiki.wikispaces.com/IDA*