八數碼問題-A*算法-Java實現

前言:本篇博客是創建在這篇博文的基礎上,是我的通過實際操做以後對其算法的改進html

1、八數碼問題

一個九宮格,有八個數字1-8已經肯定位置,剩下一個空格以0表示,0能夠和上下左右的數字交換位置。java

若是給定一個初始狀態1,一個目標狀態2,求解從狀態1到狀態2最少要移動多少步算法

 2、A*算法

一、算法定義及公式數組

算法是一種靜態路網中求解最短路徑最有效的直接搜索方法,公式表示爲: f(n)=g(n)+h(n),其中:ide

  • f(n) 是從初始狀態經由狀態n到目標狀態的代價估計,稱做估計函數
  • d(n) 是在狀態空間從初始狀態到狀態n的實際代價,稱做節點深度
  • h(n) 是從狀態n到目標狀態的最佳路徑的估計代價,稱做啓發函數
(對於路徑搜索問題,狀態就是圖中的節點,代價就是距離)
二、A*算法設計

 3、八數碼問題算法設計步驟

一、設計八數碼節點狀態結構函數

存儲結構採起一維數組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("目標狀態不可達");
    }
}
View Code
相關文章
相關標籤/搜索