前言:本篇博客是創建在這篇博文的基礎上,是我的通過實際操做以後對其算法的改進html
一個九宮格,有八個數字1-8已經肯定位置,剩下一個空格以0表示,0能夠和上下左右的數字交換位置。java
若是給定一個初始狀態1,一個目標狀態2,求解從狀態1到狀態2最少要移動多少步算法
一、算法定義及公式數組
算法是一種靜態路網中求解最短路徑最有效的直接搜索方法,公式表示爲: f(n)=g(n)+h(n),其中:ide
一、設計八數碼節點狀態結構函數
存儲結構採起一維數組int[] num,估計函數f(n)、節點深度d(n)、啓發函數h(n),以及要定義每個狀態的父狀態。this
這裏額外設置了一個answer的線性表,用於存儲最終答案路徑(爲何額外設計它後面會解釋)lua
後面的getter和setter不額外截圖了spa
二、可達性判斷函數設計(計算兩個節點之間的可達性)設計
這裏是經過計算八數碼節點的逆序數判斷,兩個節點狀態的逆序數必須同奇或者同偶纔是可達狀態(也就是初始狀態的逆序數+目標狀態的逆序數=偶數)。什麼是逆序數呢?
若是一對數的先後位置與大小順序相反,也就是若是較大的數在較小的數以前,那麼就算一個逆序對,逆序數+1。逆序數是偶數的排列稱做偶排列,是奇數的排列稱做奇排列。如在 2,4,3,1 中,21,43,41,31是逆序,逆序數是4,是偶排列。計算八數碼節點的逆序數時必需要把表明空格的0去掉,這裏是原博客沒有注意的地方,判斷可達性的函數修改以下:
三、估計函數的設計
估計函數是由兩部分構成的,節點深度d(n)其實就是當前已經走的步數,不用額外設計函數;啓發函數h(n)是比較重要的一個部分,啓發函數的設計直接影響了估計函數的效率,有幾種定義方法:
這裏選擇的是第一種,計算當前節點與目標節點相比,有多少個數字的位置不符
四、A*算法設計
依照上面算法流程圖設計就能夠了。額外須要注意的是:
open表和close表使用ArrayList保存狀態,open表存放全部的狀態,close表則存放在搜索過程當中一些較優的狀態,可是close表並非最終咱們走的路徑。由於咱們要在搜索完成找到目標狀態以後,根據父狀態還原出原路徑,所以原博客中的算法只能逆序輸出路徑。因此我額外設計了一個線性表answer就是爲了在還原過程當中保存咱們所走的路徑,從而實現正序的輸出。對輸出路徑的函數修改:
五、其餘邏輯函數設計
對子狀態的f(n)的排序
根據八數碼肯定0在一維數組中下標的函數
判斷當前狀態是否在open表中的去重函數
判斷移動的函數(判斷是否能夠上、下、左、右移動)
實現移動的函數(實現0在八數碼中的上、下、左、右移動)
根據一維數組輸出八數碼(3*3)格式的函數
六、源碼
import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Scanner; @SuppressWarnings("rawtypes") public class EightPuzzle implements Comparable{ private int[] num = new int[9]; private int evaluation; //估計函數f(n):從起始狀態到目標的最小估計值 private int depth; //d(n):當前的深度,即走到當前狀態的步驟 private int misposition; //啓發函數 h(n):到目標的最小估計(記錄和目標狀態有多少個數不一樣) private EightPuzzle parent; //當前狀態的父狀態 private ArrayList<EightPuzzle> answer = new ArrayList<EightPuzzle>(); //保存最終路徑 public int[] getNum() { return num; } public void setNum(int[] num) { this.num = num; } public int getDepth() { return depth; } public void setDepth(int depth) { this.depth = depth; } public int getEvaluation() { return evaluation; } public void setEvaluation(int evaluation) { this.evaluation = evaluation; } public int getMisposition() { return misposition; } public void setMisposition(int misposition) { this.misposition = misposition; } public EightPuzzle getParent() { return parent; } public void setParent(EightPuzzle parent) { this.parent = parent; } /** * 判斷當前狀態是否爲目標狀態 * @param target * @return */ public boolean isTarget(EightPuzzle target){ return Arrays.equals(getNum(), target.getNum()); } /** * 求估計函數f(n) = g(n)+h(n); * 初始化狀態信息 * @param target */ public void init(EightPuzzle target){ int temp = 0; for(int i=0;i<9;i++){ if(num[i]!=target.getNum()[i]) temp++; //記錄當前節點與目標節點差別的度量 } this.setMisposition(temp); if(this.getParent()==null){ this.setDepth(0); //初始化步數(深度) }else{ this.depth = this.parent.getDepth()+1;//記錄步數 } this.setEvaluation(this.getDepth()+this.getMisposition());//返回當前狀態的估計值 } /** * 求逆序值並判斷是否有解,逆序值同奇或者同偶纔有解 * @param target * @return 有解:true 無解:false */ public boolean isSolvable(EightPuzzle target){ int reverse = 0; for(int i=0;i<9;i++){ for(int j=0;j<i;j++){//遇到0跳過 if(num[j]>num[i] && num[j]!=0 && num[i]!= 0) reverse++; if(target.getNum()[j]>target.getNum()[i] && target.getNum()[j]!=0 && target.getNum()[i]!=0) reverse++; } } if(reverse % 2 == 0) return true; return false; } /** * 對每一個子狀態的f(n)進行由小到大排序 * */ @Override public int compareTo(Object o) { EightPuzzle c = (EightPuzzle) o; return this.evaluation-c.getEvaluation();//默認排序爲f(n)由小到大排序 } /** * @return 返回0在八數碼中的位置 */ public int getZeroPosition(){ int position = -1; for(int i=0;i<9;i++){ if(this.num[i] == 0){ position = i; } } return position; } /** * 去重,當前狀態不重複返回-1 * @param open 狀態集合 * @return 判斷當前狀態是否存在於open表中 */ public int isContains(ArrayList<EightPuzzle> open){ for(int i=0; i<open.size(); i++){ if(Arrays.equals(open.get(i).getNum(), getNum())){ return i; } } return -1; } /** * 一維數組 * @return 小於3(第一行)的不能上移返回false */ public boolean isMoveUp() { int position = getZeroPosition(); if(position<=2){ return false; } return true; } /** * * @return 大於6(第三行)返回false */ public boolean isMoveDown() { int position = getZeroPosition(); if(position>=6){ return false; } return true; } /** * * @return 0,3,6(第一列)返回false */ public boolean isMoveLeft() { int position = getZeroPosition(); if(position%3 == 0){ return false; } return true; } /** * * @return 2,5,8(第三列)不能右移返回false */ public boolean isMoveRight() { int position = getZeroPosition(); if((position)%3 == 2){ return false; } return true; } /** * * @param move 0:上,1:下,2:左,3:右 * @return 返回移動後的狀態 */ public EightPuzzle moveUp(int move){ EightPuzzle temp = new EightPuzzle(); int[] tempnum = (int[])num.clone(); temp.setNum(tempnum); int position = getZeroPosition(); //0的位置 int p=0; //與0換位置的位置 switch(move){ case 0: p = position-3; temp.getNum()[position] = num[p]; break; case 1: p = position+3; temp.getNum()[position] = num[p]; break; case 2: p = position-1; temp.getNum()[position] = num[p]; break; case 3: p = position+1; temp.getNum()[position] = num[p]; break; } temp.getNum()[p] = 0; return temp; } /** * 按照3*3格式輸出 */ public void print(){ for(int i=0;i<9;i++){ if(i%3 == 2){ System.out.println(this.num[i]); }else{ System.out.print(this.num[i]+" "); } } } /** * 將最終答案路徑保存下來並輸出 */ public void printRoute(){ EightPuzzle temp = null; int count = 0; temp = this; System.out.println("----------開始移動----------"); while(temp!=null){ answer.add(temp); temp = temp.getParent(); count++; } for(int i=answer.size()-1 ; i>=0 ; i--){ answer.get(i).print(); System.out.println("--------------------"); } System.out.println("最小移動步數:"+(count-1)); } /** * * @param open open表 * @param close close表 * @param parent 父狀態 * @param target 目標狀態 */ public void operation(ArrayList<EightPuzzle> open,ArrayList<EightPuzzle> close,EightPuzzle parent,EightPuzzle target){ if(this.isContains(close) == -1){//若是不在close表中 int position = this.isContains(open);//獲取在open表中的位置 if(position == -1){//若是也不在open表中 this.parent = parent;//指明它的父狀態 this.init(target);//計算它的估計值 open.add(this);//把它添加進open表 }else{//若是它在open表中 if(this.getDepth() < open.get(position).getDepth()){//跟已存在的狀態做比較,若是它的步數較少則是較優解 open.remove(position);//把已經存在的相同狀態替換掉 this.parent = parent; this.init(target); open.add(this); } } } } @SuppressWarnings("unchecked") public static void main(String args[]){ //定義open表 ArrayList<EightPuzzle> open = new ArrayList<EightPuzzle>(); ArrayList<EightPuzzle> close = new ArrayList<EightPuzzle>(); EightPuzzle start = new EightPuzzle(); EightPuzzle target = new EightPuzzle(); // int stnum[] = {8,6,7,2,5,4,3,0,1}; // int tanum[] = {6,4,7,8,5,0,3,2,1}; Scanner s = new Scanner(System.in); int stnum[] = new int[9]; int tanum[] = new int[9]; System.out.println("請輸入初始狀態:"); for(int i = 0; i< 9; i++){ stnum[i] = s.nextInt(); } System.out.println("請輸入目標狀態:"); for(int j= 0; j< 9; j++){ tanum[j] = s.nextInt(); } s.close(); start.setNum(stnum); target.setNum(tanum); long startTime=System.currentTimeMillis(); if(start.isSolvable(target)){ //初始化初始狀態 start.init(target); open.add(start); while(open.isEmpty() == false){ Collections.sort(open); //按照evaluation的值排序 EightPuzzle best = open.get(0); //從open表中取出最小估值的狀態並移出open表 open.remove(0); close.add(best); if(best.isTarget(target)){ //輸出 best.printRoute(); long end=System.currentTimeMillis(); System.out.println("程序運行 "+ (end-startTime) +" ms"); System.exit(0); } int move; //由best狀態進行擴展並加入到open表中 //0的位置上移以後狀態不在close和open中設定best爲其父狀態,並初始化f(n)估值函數 if(best.isMoveUp()){//能夠上移的話 move = 0;//上移標記 EightPuzzle up = best.moveUp(move);//best的一個子狀態 up.operation(open, close, best, target); } //0的位置下移以後狀態不在close和open中設定best爲其父狀態,並初始化f(n)估值函數 if(best.isMoveDown()){ move = 1; EightPuzzle down = best.moveUp(move); down.operation(open, close, best, target); } //0的位置左移以後狀態不在close和open中設定best爲其父狀態,並初始化f(n)估值函數 if(best.isMoveLeft()){ move = 2; EightPuzzle left = best.moveUp(move); left.operation(open, close, best, target); } //0的位置右移以後狀態不在close和open中設定best爲其父狀態,並初始化f(n)估值函數 if(best.isMoveRight()){ move = 3; EightPuzzle right = best.moveUp(move); right.operation(open, close, best, target); } } }else System.out.println("目標狀態不可達"); } }