AStar解決八數碼問題(java實現)
阿新 • • 發佈:2019-02-02
八數碼遊戲(八數碼問題)描述為:在3×3組成的九宮格棋盤上,擺有八個將牌,每一個將牌都刻有1-8八個數碼中的某一個數碼。棋盤中留有一個空格,允許其周圍的某一個將牌向空格移動,這樣通過移動將牌就可以不斷改變將牌的佈局。這種遊戲求解的問題是:給定一種初始的將牌佈局或結構(稱初始狀態)和一個目標的佈局(稱目標狀態),問如何移動將牌,實現從初始狀態到目標狀態的轉變。
對於八數碼問題的解決,首先要考慮是否有答案。每一個狀態可認為是一個1×9的矩陣,問題即通過矩陣的變換,是否可以變換為目標狀態對應的矩陣?由數學知識可知,可計算這兩個有序數列的逆序值,如果兩者都是偶數或奇數,則可通過變換到達,否則,這兩個狀態不可達。這樣,就可以在具體解決問題之前判斷出問題是否可解,從而可以避免不必要的搜尋。
常用的狀態空間搜尋有深度優先和廣度優先。廣度優先是從初始狀態一層一層向下找,直到找到目標為止。深度優先是按照一定的順序前查詢完一個分支,再查詢另一個分支,以至找到目標為止。 廣度和深度優先搜尋有一個很大的缺陷就是他們都是在一個給定的狀態空間中窮舉。這在狀態空間不大的情況下是很合適的演算法,可是當狀態空間十分大,且不預測的情況下就不可取了。他的效率實在太低,甚至不可完成。由於八數碼問題狀態空間共有9!個狀態,對於八數碼問題如果選定了初始狀態和目標狀態,有9!/2個狀態要搜尋,考慮到時間和空間的限制,在這裡採用A*演算法作為搜尋策略。在這裡就要用到啟發式搜尋 啟發式搜尋就是在狀態空間中的搜尋對每一個搜尋的位置進行評估,得到最好的位置,再從這個位置進行搜尋直到目標。這樣可以省略大量無畏的搜尋路徑,提到了效率。在啟發式搜尋中,對位置的估價是十分重要的。採用了不同的估價可以有不同的效果。
啟發中的估價是用估價函式表示的,如:f(n) = g(n) + h(n) 其中f(n) 是節點n的估價函式,g(n)是在狀態空間中從初始節點到n節點的實際代價,h(n)是從n到目標節點最佳路徑的估計代價。 在此八數碼問題中,顯然g(n)就是從初始狀態變換到當前狀態所移動的步數,估計代價h(n)我們就可採用當前狀態各個數字牌不在目標狀態未知的個數,即錯位數。
本演算法的設計步驟是:
初始化兩個連結串列open和closed,將初始狀態放入open表中
public class Struct { int num[] = new int[9];//狀態 Struct parent;//父節點 Struct next;//open表或closed表中的後一個節點 int fvalue;//總路徑 int gvalue;//實際路徑 int hvalue;//節點到達目標狀態的艱難程度 /** * 重寫hashcode和equals方法 */ @Override public int hashCode() { return this.hvalue; } @Override public boolean equals(Object obj) { Boolean flag = true; if(obj instanceof Struct) { Struct p = (Struct)obj; for(int i =0;i<9;i++) { if(p.num[i]!=this.num[i]) flag = false; } } else { flag=false; } return flag; } } package com.lxl; /** * 新的比較器 */ import java.util.Comparator; public class NewComparator implements Comparator<Struct> { @Override public int compare(Struct o1, Struct o2) { if(o1.fvalue>o2.fvalue) return 1; else if(o1.fvalue<o2.fvalue) return -1; else return 0; } } package com.lxl; import java.util.*; public class AStar8Num { List<Struct> open = new ArrayList<Struct>();//open表 List<Struct> closed = new ArrayList<Struct>();//closed表 List<Struct> spring = new ArrayList<Struct>();//spring表 int start[] = new int[9]; int target[] = new int[9]; Struct structOfStart = new Struct();//初始狀態 Struct structOfTarget = new Struct();//目標狀態 public void init() { int i = 0; System.out.println("輸入初始狀態:"); Scanner io = new Scanner(System.in); String s = io.nextLine(); //切分讀取的一行字串,以空格為標杆 String str[] = s.split(" "); for(String st : str) { if(!st.equals("")) { start[i++]=Integer.parseInt(st); } } System.out.println("輸入目標狀態:"); Scanner io1 = new Scanner(System.in); String s1 = io1.nextLine(); //切分讀取的一行字串,以空格為標杆 String str1[] = s1.split(" "); i=0;//還原i值 for(String st : str1) { if(!st.equals("")) { target[i++]=Integer.parseInt(st); } } /* for(i=0;i<9;i++) { System.out.print(start[i]+" "); }*/ //初始狀態 for(i = 0;i<9;i++) { structOfStart.num[i]=start[i]; } structOfStart.gvalue=0; structOfStart.hvalue=getHvalue(structOfStart); structOfStart.fvalue = structOfStart.gvalue+structOfStart.hvalue; structOfStart.parent=null; structOfStart.next=null; open.add(structOfStart);//初始狀態加入open表中 //目標狀態 for(i=0;i<9;i++) { structOfTarget.num[i]=target[i]; } structOfTarget.hvalue=getHvalue(structOfTarget); } //計算某個狀態的h值 public int getHvalue(Struct status) { int i,num=0; for(i=0;i<9;i++) { if(status.num[i]!=target[i]) num++; } status.hvalue = num; return status.hvalue; } //將某個狀態加入到open表中,需要按非遞減排序的 public void add(Struct status , List<Struct> list) { list.add(status); //需要構造新的比較器NewComparator Collections.sort(list, new NewComparator()); } //兩個結點是否有相同的狀態 public Boolean hasSameStatus(Struct s1,Struct s2) { boolean flag = true; for(int i =0 ;i<9;i++) { if(s1.num[i]!=s2.num[i]) flag = false; } return flag; } //結點與其祖先結點是否有相同的狀態 public Boolean hasAnceSameStatus(Struct origin,Struct ancester) { boolean flag = false; while(ancester!=null) { if(hasSameStatus(origin,ancester)) { flag=true; return flag; } ancester = ancester.parent;//尋找祖先結點 } return flag; } //把陣列b的值複製給陣列a public void copySnumToTnum(int a[],int b[]) { int len = b.length; for(int i = 0;i<len;i++) { a[i]=b[i]; } } //移動後產生後繼結點 public void getShift(Struct status,int index,int pos) { int medium = 0;//中介值 Struct temp = new Struct(); //temp.num = status.num;傳的是地址 //複製陣列的值 copySnumToTnum(temp.num,status.num); //outputStatus(status); //右移 if(index==1) { //交換位置 medium = temp.num[pos]; temp.num[pos] = temp.num[pos-1]; temp.num[pos-1] = medium; //如果與父輩結點沒有相同的狀態 if(!hasAnceSameStatus(temp,status.parent)) { temp.gvalue = status.gvalue+1; temp.hvalue = getHvalue(temp); temp.fvalue = temp.gvalue+temp.hvalue; temp.parent = status; temp.next=null; //加入spring表中 spring.add(0,temp); } } //下移 else if(index==2) { //交換位置 medium = temp.num[pos]; temp.num[pos] = temp.num[pos-3]; temp.num[pos-3] = medium; if(!hasAnceSameStatus(temp,status.parent)) { temp.gvalue = status.gvalue+1; temp.hvalue = getHvalue(temp); temp.fvalue = temp.gvalue+temp.hvalue; temp.parent = status; temp.next=null; //加入spring表中 spring.add(0,temp); } } //左移 else if(index==3) { //交換位置 medium = temp.num[pos]; temp.num[pos] = temp.num[pos+1]; temp.num[pos+1] = medium; if(!hasAnceSameStatus(temp,status.parent)) { temp.gvalue = status.gvalue+1; temp.hvalue = getHvalue(temp); temp.fvalue = temp.gvalue+temp.hvalue; temp.parent = status; temp.next=null; //加入spring表中 spring.add(0,temp); } } //上移 else { //交換位置 medium = temp.num[pos]; temp.num[pos] = temp.num[pos+3]; temp.num[pos+3] = medium; if(!hasAnceSameStatus(temp,status.parent)) { temp.gvalue = status.gvalue+1; temp.hvalue = getHvalue(temp); temp.fvalue = temp.gvalue+temp.hvalue; temp.parent = status; temp.next=null; //加入spring表中 spring.add(0,temp); } } } //產生後繼結點 public void getNexts(Struct status) { int pos = 0; int i; //找到空格位置 for(i=0;i<9;i++) { if(status.num[i]==0) { pos=i; break; } } //右移 if(pos%3!=0) { getShift(status,1,pos); } //下移 if(pos>2) { getShift(status, 2, pos); } //左移 if(pos%3!=2) { getShift(status, 3, pos); } //上移 if(pos<6) { getShift(status, 4, pos); } } //得到路徑 public void getPath(Struct status) { int deepnum = status.gvalue; if(status.parent!=null) { getPath(status.parent); } System.out.println("第"+deepnum+"層狀態為:"); deepnum--; outputStatus(status); } //輸出狀態 public void outputStatus(Struct status) { for(int i = 0;i<status.num.length;i++) { if(i%3==0) System.out.println(); System.out.print(status.num[i]+" "); } System.out.println(); } //判斷是否能解決 public Boolean icansolve() { boolean flag = false; int i ,j; int resultOfStart=0; int resultOfTarget = 0; for(i=0;i<9;i++) { for(j=0;j<i;j++) { if(start[j]<start[i]&&start[j]!=0) resultOfStart++; if(target[j]<target[i]&&target[j]!=0) resultOfTarget++; } } //System.out.println(resultOfStart); //System.out.println(resultOfTarget); if((resultOfStart+resultOfTarget)%2==0) flag=true; return flag; } public void reslove() { int numcount = 1; Struct getOfOpen = null; boolean flag = false; init(); //能不能解決 if(!icansolve()) { System.out.println("不能解決!!"); System.exit(0); } System.out.println("從表中拿出的結點的狀態及相應的值:"); while(!open.isEmpty()) { getOfOpen = open.get(0); closed.add(getOfOpen); open.remove(0);//移去加入到closed表中的結點 System.out.println("第"+numcount+++"個狀態是:"); outputStatus(getOfOpen); System.out.println("其f值為:"+getOfOpen.fvalue); System.out.println("其g值為:"+getOfOpen.gvalue); System.out.println("其h值為:"+getOfOpen.hvalue); if(hasSameStatus(getOfOpen,structOfTarget)) { flag = true; break; } getNexts(getOfOpen);//產生後繼結點 while(!spring.isEmpty()) { //得到spring表中的結點 Struct struct = spring.get(0); if(open.contains(struct)) { //得到open表中相同的結點,注意這裡重寫了equals和hashcode方法 Struct structInOpen = open.get(open.indexOf(struct)); //改變open表中節點的parent指標及相關引數 if(struct.gvalue<structInOpen.gvalue) { structInOpen.parent = struct.parent; structInOpen.fvalue = struct.fvalue; structInOpen.gvalue = struct.gvalue; //在這裡是不是應該重新排序open表?????? Collections.sort(open, new NewComparator()); } //刪除spring表中的該節點 spring.remove(struct); } else if(closed.contains(struct)) { //得到closed表中相同的結點,注意這裡重寫了equals和hashcode方法 Struct structInClosed = closed.get(closed.indexOf(struct)); //改變closed表中節點的parent指標及相關引數 if(struct.gvalue<structInClosed.gvalue) { structInClosed.parent = struct.parent; structInClosed.fvalue = struct.fvalue; structInClosed.gvalue = struct.gvalue; //加入至open表中 add(structInClosed,open); } //刪除spring表中的該節點 spring.remove(struct); } else { add(struct,open); spring.remove(struct); } } } if(flag) { System.out.println("*************************************"); System.out.println("路徑長度為:"+getOfOpen.gvalue); getPath(getOfOpen); } } public static void main(String[] args) { new AStar8Num().reslove(); } }