从拼图游戏开始(四)_IDA*算法求解Java实现

       终于,在学习了完深搜和广搜、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*

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值