A*算法解決八數碼問題 Java語言實現

0X00  定義

  首先要明確一下什麼是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

0X01  分析八數碼問題

  首先咱們要簡化一下八數碼問題,咱們移動數字就是至關於移動空格。這樣咱們就將問題簡化爲空格的移動,空格移動的狀態只有4種:上、下、左、右。然而在八數碼問題中並非每次空格的移動都有四種狀態,咱們要判斷在當前位置也移動的狀態才能移動,咱們還要去掉一種狀態就是當前狀態的父狀態,由於若是咱們移動到父狀態則至關於回退了一步。lua

  而後,咱們要關心的就是給定的初始化狀態是否可以經過移動而達到目標狀態。這就涉及到了數學問題,就是若是初始狀態和目標狀態的逆序值同爲奇數或同爲偶數則能夠經過有限次數的移動到達目標狀態,不然無解。spa

  既然咱們已經清楚了空格移動的方式,咱們討論一下空格的幾種移動的可能方式: 3d

對應的狀態如圖所示。code

0X02 算法的實現

  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屬相找到路徑並輸出。

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 }
View Code
相關文章
相關標籤/搜索