首先要明確一下什麼是A*算法和八數碼問題?java
A*(A-Star)算法是一種靜態路網中求解最短路徑最有效的直接搜索方法也是一種啓發性的算法,也是解決許多搜索問題的有效算法。算法中的距離估算值與實際值越接近,最終搜索速度越快。啓發中的估價是用估價函數表示的,如: 算法
f(n) = g(n) + h(n)
其中f(n) 是節點n的估價函數,g(n)實在狀態空間中從初始節點到n節點的實際代價,h(n)是從n到目 標節點最佳路徑的估計代價。其中最重要的是h(n)函數,要求ide
h(n)<h'(n)
其中h'(n)爲實際中當前狀態要到達目標狀態的步驟數。函數
八數碼問題就是在一個3*3的矩陣中擺放1-8一共八個數字,還有一個空餘的位置用於移動來改變當前的位置來達到最終的狀態。以下圖this
首先咱們要簡化一下八數碼問題,咱們移動數字就是至關於移動空格。這樣咱們就將問題簡化爲空格的移動,空格移動的狀態只有4種:上、下、左、右。然而在八數碼問題中並非每次空格的移動都有四種狀態,咱們要判斷在當前位置也移動的狀態才能移動,咱們還要去掉一種狀態就是當前狀態的父狀態,由於若是咱們移動到父狀態則至關於回退了一步。lua
而後,咱們要關心的就是給定的初始化狀態是否可以經過移動而達到目標狀態。這就涉及到了數學問題,就是若是初始狀態和目標狀態的逆序值同爲奇數或同爲偶數則能夠經過有限次數的移動到達目標狀態,不然無解。spa
既然咱們已經清楚了空格移動的方式,咱們討論一下空格的幾種移動的可能方式: 3d
對應的狀態如圖所示。code
A*算法的實現有一個具體的流程圖:orm
咱們使用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屬相找到路徑並輸出。
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 }