A*算法解决八数码问题 Java语言实现
0X00 定义
首先要明确一下什么是A*算法和八数码问题?
A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法也是一种启发性的算法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快。启发中的估价是用估价函数表示的,如:
f(n) = g(n) + h(n)
其中f(n) 是节点n的估价函数,g(n)实在状态空间中从初始节点到n节点的实际代价,h(n)是从n到目 标节点最佳路径的估计代价。其中最重要的是h(n)函数,要求
h(n)<h'(n)
其中h'(n)为实际中当前状态要到达目标状态的步骤数。
八数码问题就是在一个3*3的矩阵中摆放1-8一共八个数字,还有一个空余的位置用于移动来改变当前的位置来达到最终的状态。如下图
0X01 分析八数码问题
首先我们要简化一下八数码问题,我们移动数字就是相当于移动空格。这样我们就将问题简化为空格的移动,空格移动的状态只有4种:上、下、左、右。然而在八数码问题中并不是每次空格的移动都有四种状态,我们要判断在当前位置也移动的状态才能移动,我们还要去掉一种状态就是当前状态的父状态,因为如果我们移动到父状态则相当于回退了一步。
然后,我们要关心的就是给定的初始化状态是否能够通过移动而达到目标状态。这就涉及到了数学问题,就是如果初始状态和目标状态的逆序值同为奇数或同为偶数则可以通过有限次数的移动到达目标状态,否则无解。
既然我们已经清楚了空格移动的方式,我们讨论一下空格的几种移动的可能方式:
对应的状态如图所示。
0X02 算法的实现
A*算法的实现有一个具体的流程图:
我们使用A*来解决八数码问题,首先我们定义一下f(n),g(n)和h(n)。
f(n):估计从初始状态到目标状态的代价。
g(n):从初始状态到当前状态的实际代价。
h(n):当前状态与目标状态的错位数。
首先我们定义八数码一个状态中的属性:
1 private int[] num = new int[9]; 2 private int depth; //当前的深度即走到当前状态的步骤 3 private int evaluation; //从起始状态到目标的最小估计值 4 private int misposition; //到目标的最小估计 5 private EightPuzzle parent; //当前状态的父状态
然后定义状态初始化信息:
1 /** 2 * 求f(n) = g(n)+h(n); 3 * 初始化状态信息 4 * @param target 5 */ 6 public void init(EightPuzzle target){ 7 int temp = 0; 8 for(int i=0;i<9;i++){ 9 if(num[i]!=target.getNum()[i]) 10 temp++; 11 } 12 this.setMisposition(temp); 13 if(this.getParent()==null){ 14 this.setDepth(0); 15 }else{ 16 this.depth = this.parent.getDepth()+1; 17 } 18 this.setEvaluation(this.getDepth()+this.getMisposition()); 19 }
如果能够找到目标状态,将会通过parent属相找到路径并输出。
0X03 代码实现
1 import java.io.BufferedReader; 2 import java.io.FileNotFoundException; 3 import java.io.FileReader; 4 import java.io.IOException; 5 import java.util.ArrayList; 6 import java.util.Arrays; 7 import java.util.Collections; 8 import java.util.Scanner; 9 10 @SuppressWarnings("rawtypes") 11 public class EightPuzzle implements Comparable{ 12 private int[] num = new int[9]; 13 private int depth; //当前的深度即走到当前状态的步骤 14 private int evaluation; //从起始状态到目标的最小估计值 15 private int misposition; //到目标的最小估计 16 private EightPuzzle parent; //当前状态的父状态 17 public int[] getNum() { 18 return num; 19 } 20 public void setNum(int[] num) { 21 this.num = num; 22 } 23 public int getDepth() { 24 return depth; 25 } 26 public void setDepth(int depth) { 27 this.depth = depth; 28 } 29 public int getEvaluation() { 30 return evaluation; 31 } 32 public void setEvaluation(int evaluation) { 33 this.evaluation = evaluation; 34 } 35 public int getMisposition() { 36 return misposition; 37 } 38 public void setMisposition(int misposition) { 39 this.misposition = misposition; 40 } 41 public EightPuzzle getParent() { 42 return parent; 43 } 44 public void setParent(EightPuzzle parent) { 45 this.parent = parent; 46 } 47 48 /** 49 * 判断当前状态是否为目标状态 50 * @param target 51 * @return 52 */ 53 public boolean isTarget(EightPuzzle target){ 54 return Arrays.equals(getNum(), target.getNum()); 55 } 56 57 /** 58 * 求f(n) = g(n)+h(n); 59 * 初始化状态信息 60 * @param target 61 */ 62 public void init(EightPuzzle target){ 63 int temp = 0; 64 for(int i=0;i<9;i++){ 65 if(num[i]!=target.getNum()[i]) 66 temp++; 67 } 68 this.setMisposition(temp); 69 if(this.getParent()==null){ 70 this.setDepth(0); 71 }else{ 72 this.depth = this.parent.getDepth()+1; 73 } 74 this.setEvaluation(this.getDepth()+this.getMisposition()); 75 } 76 77 /** 78 * 求逆序值并判断是否有解 79 * @param target 80 * @return 有解:true 无解:false 81 */ 82 public boolean isSolvable(EightPuzzle target){ 83 int reverse = 0; 84 for(int i=0;i<9;i++){ 85 for(int j=0;j<i;j++){ 86 if(num[j]>num[i]) 87 reverse++; 88 if(target.getNum()[j]>target.getNum()[i]) 89 reverse++; 90 } 91 } 92 if(reverse % 2 == 0) 93 return true; 94 return false; 95 } 96 @Override 97 public int compareTo(Object o) { 98 EightPuzzle c = (EightPuzzle) o; 99 return this.evaluation-c.getEvaluation();//默认排序为f(n)由小到大排序 100 } 101 /** 102 * @return 返回0在八数码中的位置 103 */ 104 public int getZeroPosition(){ 105 int position = -1; 106 for(int i=0;i<9;i++){ 107 if(this.num[i] == 0){ 108 position = i; 109 } 110 } 111 return position; 112 } 113 /** 114 * 115 * @param open 状态集合 116 * @return 判断当前状态是否存在于open表中 117 */ 118 public int isContains(ArrayList<EightPuzzle> open){ 119 for(int i=0;i<open.size();i++){ 120 if(Arrays.equals(open.get(i).getNum(), getNum())){ 121 return i; 122 } 123 } 124 return -1; 125 } 126 /** 127 * 128 * @return 小于3的不能上移返回false 129 */ 130 public boolean isMoveUp() { 131 int position = getZeroPosition(); 132 if(position<=2){ 133 return false; 134 } 135 return true; 136 } 137 /** 138 * 139 * @return 大于6返回false 140 */ 141 public boolean isMoveDown() { 142 int position = getZeroPosition(); 143 if(position>=6){ 144 return false; 145 } 146 return true; 147 } 148 /** 149 * 150 * @return 0,3,6返回false 151 */ 152 public boolean isMoveLeft() { 153 int position = getZeroPosition(); 154 if(position%3 == 0){ 155 return false; 156 } 157 return true; 158 } 159 /** 160 * 161 * @return 2,5,8不能右移返回false 162 */ 163 public boolean isMoveRight() { 164 int position = getZeroPosition(); 165 if((position)%3 == 2){ 166 return false; 167 } 168 return true; 169 } 170 /** 171 * 172 * @param move 0:上,1:下,2:左,3:右 173 * @return 返回移动后的状态 174 */ 175 public EightPuzzle moveUp(int move){ 176 EightPuzzle temp = new EightPuzzle(); 177 int[] tempnum = (int[])num.clone(); 178 temp.setNum(tempnum); 179 int position = getZeroPosition(); //0的位置 180 int p=0; //与0换位置的位置 181 switch(move){ 182 case 0: 183 p = position-3; 184 temp.getNum()[position] = num[p]; 185 break; 186 case 1: 187 p = position+3; 188 temp.getNum()[position] = num[p]; 189 break; 190 case 2: 191 p = position-1; 192 temp.getNum()[position] = num[p]; 193 break; 194 case 3: 195 p = position+1; 196 temp.getNum()[position] = num[p]; 197 break; 198 } 199 temp.getNum()[p] = 0; 200 return temp; 201 } 202 /** 203 * 按照八数码的格式输出 204 */ 205 public void print(){ 206 for(int i=0;i<9;i++){ 207 if(i%3 == 2){ 208 System.out.println(this.num[i]); 209 }else{ 210 System.out.print(this.num[i]+" "); 211 } 212 } 213 } 214 /** 215 * 反序列的输出状态 216 */ 217 public void printRoute(){ 218 EightPuzzle temp = null; 219 int count = 0; 220 temp = this; 221 while(temp!=null){ 222 temp.print(); 223 System.out.println("----------分割线----------"); 224 temp = temp.getParent(); 225 count++; 226 } 227 System.out.println("步骤数:"+(count-1)); 228 } 229 /** 230 * 231 * @param open open表 232 * @param close close表 233 * @param parent 父状态 234 * @param target 目标状态 235 */ 236 public void operation(ArrayList<EightPuzzle> open,ArrayList<EightPuzzle> close,EightPuzzle parent,EightPuzzle target){ 237 if(this.isContains(close) == -1){ 238 int position = this.isContains(open); 239 if(position == -1){ 240 this.parent = parent; 241 this.init(target); 242 open.add(this); 243 }else{ 244 if(this.getDepth() < open.get(position).getDepth()){ 245 open.remove(position); 246 this.parent = parent; 247 this.init(target); 248 open.add(this); 249 } 250 } 251 } 252 } 253 254 @SuppressWarnings("unchecked") 255 public static void main(String args[]){ 256 //定义open表 257 ArrayList<EightPuzzle> open = new ArrayList<EightPuzzle>(); 258 ArrayList<EightPuzzle> close = new ArrayList<EightPuzzle>(); 259 EightPuzzle start = new EightPuzzle(); 260 EightPuzzle target = new EightPuzzle(); 261 262 //BufferedReader br = new BufferedReader(new FileReader("./input.txt") ); 263 String lineContent = null; 264 int stnum[] = {2,1,6,4,0,8,7,5,3}; 265 int tanum[] = {1,2,3,8,0,4,7,6,5}; 266 int order = 0; 267 try { 268 BufferedReader br; 269 br = new BufferedReader(new FileReader("input.txt") ); 270 while((lineContent=br.readLine())!=null){ 271 String[] str = lineContent.split(","); 272 for(int i = 0 ;i<str.length;i++){ 273 if(order==0) 274 stnum[i] = Integer.parseInt(str[i]); 275 else 276 tanum[i] = Integer.parseInt(str[i]); 277 } 278 order++; 279 } 280 } catch (NumberFormatException e) { 281 System.out.println("请检查输入文件的格式,例如:2,1,6,4,0,8,7,5,3 换行 1,2,3,8,0,4,7,6,5"); 282 e.printStackTrace(); 283 } catch (IOException e) { 284 System.out.println("当前目录下无input.txt文件。"); 285 e.printStackTrace(); 286 } 287 start.setNum(stnum); 288 target.setNum(tanum); 289 long startTime=System.currentTimeMillis(); //获取开始时间 290 if(start.isSolvable(target)){ 291 //初始化初始状态 292 start.init(target); 293 open.add(start); 294 while(open.isEmpty() == false){ 295 Collections.sort(open); //按照evaluation的值排序 296 EightPuzzle best = open.get(0); //从open表中取出最小估值的状态并移除open表 297 open.remove(0); 298 close.add(best); 299 if(best.isTarget(target)){ 300 //输出 301 best.printRoute(); 302 long end=System.currentTimeMillis(); //获取结束时间 303 System.out.println("程序运行时间: "+(end-startTime)+"ms"); 304 System.exit(0); 305 } 306 int move; 307 //由best状态进行扩展并加入到open表中 308 //0的位置上移之后状态不在close和open中设定best为其父状态,并初始化f(n)估值函数 309 if(best.isMoveUp()){ 310 move = 0; 311 EightPuzzle up = best.moveUp(move); 312 up.operation(open, close, best, target); 313 } 314 //0的位置下移之后状态不在close和open中设定best为其父状态,并初始化f(n)估值函数 315 if(best.isMoveDown()){ 316 move = 1; 317 EightPuzzle up = best.moveUp(move); 318 up.operation(open, close, best, target); 319 } 320 //0的位置左移之后状态不在close和open中设定best为其父状态,并初始化f(n)估值函数 321 if(best.isMoveLeft()){ 322 move = 2; 323 EightPuzzle up = best.moveUp(move); 324 up.operation(open, close, best, target); 325 } 326 //0的位置右移之后状态不在close和open中设定best为其父状态,并初始化f(n)估值函数 327 if(best.isMoveRight()){ 328 move = 3; 329 EightPuzzle up = best.moveUp(move); 330 up.operation(open, close, best, target); 331 } 332 333 } 334 }else 335 System.out.println("没有解,请重新输入。"); 336 } 337 338 }