人工智慧之八數碼問題
八數碼難題實驗報告
問題描述
八數碼難題
3×3九宮棋盤,放置數碼為1 - 8的8個棋牌,剩下一個空格,只能通過棋牌向空格的移動來改變棋盤的佈局。
- 求解的問題——給定初始佈局(即初始狀態)和目標佈局(即目標狀態),如何移動棋牌才能從初始佈局到達目標佈局
· 解答路徑——就是一個合法的走步序列
初始狀態S0: 2 3 目標狀態Sg:1 2 3
1 8 4 8 0 4
7 6 5 7 6 5
問題分析
採用啟發式搜尋的方式解決此問題,利用A*演算法,通過估價函式來選擇一條最佳路徑。
• 估價函式的定義:
對節點n定義f*(n)=g*(n)+h*(n),表示從S開始約束通過節點n的一條最佳路徑的代價。
希望估價函式f 定義為:f(n)=g(n)+h(n)
—— g是g*的估計 ,h是h*的估計
• g*(n):從s到n的最小路徑代價值
• h*(n):從n到g的最小路徑代價值
• f*(n)=g*(n)+h*(n):從s經過n到g的最小路徑的總代價值
• g(n)、h(n)、f(n)分別是g*(n)、h*(n)、f*(n)的估計值
• g(n)通常選擇為當前所找到的從初始節點S到節點n的“最佳”路徑的代價值,顯然有g(n) ³g*(n)
具體解決思路是,通過給定的初始狀態,推算該狀態下能產生的所有可能的情況,加入到等待佇列中,通過A*演算法來計算這些狀態接近結果的距離大概是多少,然後根據這個距離來選取最小的狀態繼續搜尋,如此迴圈,直到找到結果。
下面是具體實現程式碼,首先是主流程類:
import java.util.ArrayList;
import java.util.Scanner;
/**
* 程式入口,這裡主要是輸入初始資料
* @author 41571
*2 8 3 1 6 4 7 0 5
*/
public class Project {
private int total = 0;
public static int[][] Goal = {{1,2,3},{8,0,4},{7,6,5}};
private Node ini;
private ArrayList<Node> nodeList = new ArrayList<Node>();//需要檢測的情況
private ArrayList<Node> result = new ArrayList<Node>();//檢測過的情況
private ArrayList<Node> end = new ArrayList<Node>();//結果
public Project(){ //初始化最初狀態
System.out.println("輸入初始狀態,0表示空格");
Scanner in = new Scanner(System.in);
String numString = in.nextLine();
ini = new Node(numString);
nodeList.add(ini);
Looking();
}
public static void main(String args[]){ //主函式
new Project();
}
public void Looking(){ //遍歷,根據權值遍歷
Node pro;boolean a = true;
while(a){
sort(); //氣泡排序,把權值最小的放在佇列最後
pro = nodeList.get(nodeList.size() - 1); //獲得棧頂元素
if(isSame(pro)){ //當前要檢測的節點是否已經遍歷過了
total++;
result.add(pro); //先加入檢測過的佇列
int[][] data = pro.getNum().getData(); //獲得當前檢測的節點資料
int deep = pro.getDepth(); //獲得當前檢測的節點的深度
nodeList.remove(nodeList.size() - 1); //把當前節點移出遍歷佇列
if(pro.getNum().isSame(Goal)){ //當前節點是否是目標
print();
break;
}else{ //不是目標,開始生成移動後的節點
if(pro.getNum().isUp()){
int[][] da = new int[3][3];
for(int i = 0;i <3;i ++)for(int j = 0;j < 3;j ++){da[i][j] = data[i][j];}
Num num = new Num(da);
Node node = new Node(num.up(),deep+1,pro);
if(isSame(node)){
nodeList.add(node); //生成移動後的節點並把當前節點作為父節點加入其中
total++;
}
}
if(pro.getNum().isDown()){
int[][] da = new int[3][3];
for(int i = 0;i <3;i ++)for(int j = 0;j < 3;j ++){da[i][j] = data[i][j];}
Num num = new Num(da);
Node node = new Node(num.down(),deep+1,pro);
if(isSame(node)){
nodeList.add(node);
total++;
}
}
if(pro.getNum().isLift()){
int[][] da = new int[3][3];
for(int i = 0;i <3;i ++)for(int j = 0;j < 3;j ++){da[i][j] = data[i][j];}
Num num = new Num(da);
Node node = new Node(num.lift(),deep+1,pro);
if(isSame(node)){
nodeList.add(node);
total++;
}
}
if(pro.getNum().isRight()){
int[][] da = new int[3][3];
for(int i = 0;i <3;i ++)for(int j = 0;j < 3;j ++){da[i][j] = data[i][j];}
Num num = new Num(da);
Node node = new Node(num.right(),deep+1,pro);
if(isSame(node)){
nodeList.add(node);
total++;
}
}
}
}else //已經遍歷過這個節點,直接移除
nodeList.remove(nodeList.size() - 1);
}
}
private boolean isSame(Node pro) { //判斷pro是否遍歷過,即是否出現在遍歷過的佇列中
// TODO Auto-generated method stub
for(int i = 0;i < result.size();i ++){
if(result.get(i).getNum().isSame(pro.getNum().getData())){
return false;
}
}
return true;
}
private void print() { //輸出結果函式
// TODO Auto-generated method stub
delEnd(result.get(result.size()-1));
for(int i = end.size()-1;i > 0 ;i --){
System.out.println("第"+(end.size()-i)+"步--------------------");
end.get(i).getNum().show();
}
/*for(int i = 0;i < result.size();i ++){
System.out.println("第"+(i+1)+"步--------------------");
result.get(i).getNum().show();
}*/
System.out.println("第"+(end.size())+"步--------------------");
for(int i = 0;i < 3;i ++){
for(int j = 0;j < 3;j ++){
if(Goal[i][j] == 0){
System.out.print(" ");
}
else
System.out.print(Goal[i][j]+" ");
}
System.out.println("");
}
System.out.println("一共查詢了"+result.size()+"個點.");
System.out.println("一共產生了"+total+"個點.");
}
private void delEnd(Node node){ //除去多餘的步驟,採用遞迴,從後向前根據父節點找出最短的路徑
end.add(node);
if(node.getFather()!=null){
delEnd(node.getFather());
}
}
private void sort() { //氣泡排序,由大到小
// TODO Auto-generated method stub
Node node;
if(nodeList.size()>1)
for(int i = 0;i < nodeList.size();i ++){
for(int j = i+1;j < nodeList.size();j ++){
if(nodeList.get(i).getValue()<nodeList.get(j).getValue()){
node = nodeList.get(i);
nodeList.set(i, nodeList.get(j));
nodeList.set(j, node);
}
}
}
}
}
這裡是節點類Node:
/**
* 八數碼的每個節點,節點內容包括
* 資料num
* 深度depth
* 總權重value
* 每個位置距離目標位置的和weight
* 兩個建構函式,引數是輸入的字串,因為只有初始狀態會用到這個建構函式,所以深度會是0
* 另一個建構函式,Num物件和深度depth
* 具有的方法有
* 從輸入的資訊提取資料num的getNum
*
* @author 41571
*
*/
public class Node {
private Num num;
private int depth = 0;
private int weight = 0;
private int value = 0;
private Node father;
public Node(Num num,int depth,Node father){ //中間遍歷需要產生的節點,這裡需要記錄父節點
this.num = num;
this.weight = this.num.getWeight();
this.depth = depth;
this.value = this.depth + this.weight;
this.father = father;
}
public Node(String numString){ //用來產生最初的根節點,這裡不需要父節點
this.num = getNum(numString);
this.weight = this.num.getWeight();
this.depth = 0;
this.value = this.depth + this.weight;
}
public Num getNum(String numString){ //把一行String資料轉化為int型陣列
int[][] num = new int[3][3]; //二維陣列
int[] nums = new int[9]; //一維陣列
char[] numChar;
numChar = numString.toCharArray();
int a = 0;
for(int i = 0;i < numChar.length;i++){
if(' ' != numChar[i]){
nums[a++] = numChar[i];
}
}
a = 0;
for(int j = 0;j < nums.length;j++){
num[a][j%3] = nums[j] - 48;
if(j%3 == 2)
a++;
}
return new Num(num);
}
public Num getNum(){
return this.num;
}
public int getDepth(){
return this.depth;
}
public int getWeight(){
return this.weight;
}
public int getValue(){
return this.value;
}
public Node getFather(){
return this.father;
}
}
為了方便對節點所儲存的狀態的操作,我新增了Num類,用來實現對具體八數碼的數字的移動:
/**
* 資料類,存放資料
* 具有的功能,空格的上下左右的移動函式
* up,down,lift,right
* 以及移動是否安全,isUp,isDown,isLift,isRight
* 以及顯示函式show
* 還有獲得每個位置距離目標位置的和的函式getWeight
* @author 41571
*
*/
public class Num {
private int [][] data;
private int [] zero = new int[2]; //0點所在的位置
public Num(int[][] data){
this.data = new int[3][3];
this.data = data;
this.zero = this.getZero();
}
public void show(){ //列印
for(int i = 0;i < this.data.length;i ++){
for(int j = 0;j < this.data[i].length;j ++){
if(data[i][j] == 0) System.out.print(" ");
else System.out.print(data[i][j]+" ");
}
System.out.println("");
}
}
public boolean isUp(){ //能上移嗎
if(zero[0] >= 1) return true;
return false;
}
public Num up(){ //上移
if(zero[0] >= 1){
this.data[zero[0]][zero[1]] = this.data[zero[0]-1][zero[1]];
this.data[zero[0]-1][zero[1]] = 0;
}
return this;
}
public boolean isDown(){ //能下移嗎
if(zero[0] <= 1) return true;
else return false;
}
public Num down(){ //下移
if(zero[0] <= 1){
this.data[zero[0]][zero[1]] = this.data[zero[0]+1][zero[1]];
this.data[zero[0]+1][zero[1]] = 0;
}
return this;
}
public boolean isLift(){ //能左移嗎
if(zero[1] >= 1) return true;
else return false;
}
public Num lift(){ //左移
if(zero[1] >= 1){
this.data[zero[0]][zero[1]] = this.data[zero[0]][zero[1]-1];
this.data[zero[0]][zero[1]-1] = 0;
}
return this;
}
public boolean isRight(){ //能右移嗎
if(zero[1] <= 1) return true;
else return false;
}
public Num right(){ //右移
if(zero[1] <= 1){
this.data[zero[0]][zero[1]] = this.data[zero[0]][zero[1]+1];
this.data[zero[0]][zero[1]+1] = 0;
}
return this;
}
public int[] getZero(){ //獲得空格即是0所在位置
int a = 0;
for(int i = 0;i < this.data.length;i ++){
for(int j = 0;j < this.data[i].length;j ++){
if(this.data[i][j] == 0){
zero[0] = i;
zero[1] = j;
a = 1;
break;
}
}
if(a == 1) break;
}
if(a == 0) System.out.println("這個八數碼中沒有空格");
return zero;
}
public int getWeight(){ //獲得每個位置距目標位置的和
int total = 0;
for(int i = 0;i < this.data.length;i ++){
for(int j = 0;j < this.data[i].length;j ++){
//System.out.print("查詢"+i+","+j+" 資料 "+data[i][j]);
if(data[i][j]!=0)
total+=getEveryoneWeight(i,j);
}
}
return total;
}
private int getEveryoneWeight(int i, int j) { //獲得每個格子上的數距原位置的移動步數
// TODO Auto-generated method stub
int row = 0,col = 0,a = 0;
for(int m = 0;m < Project.Goal.length;m ++){
for(int n = 0;n < Project.Goal[m].length;n ++){
if(this.data[i][j] == Project.Goal[m][n]){
//System.out.println(" 目標在"+m+","+n+" 資料"+Project.Goal[m][n]);
row = i - m;
col = j - n;
a = 1;
break;
}
}
if(a == 1) break;
}
if(row < 0) row = 0 - row;
if(col < 0) col = 0 - col;
return (row + col);
}
public int[][] getData(){
return this.data;
}
public boolean isSame(int[][] same){ //判斷是否一樣
for(int i = 0;i <same.length;i ++){
for(int j = 0;j < same.length;j ++){
if(same[i][j]!=this.data[i][j])
return false;
}
}
return true;
}
}
相關文章
- 八數碼 經典問題
- 用C語言實現八數碼問題C語言
- ORACLE常用傻瓜問題1000問(之八)(轉)Oracle
- ORACLE常用傻瓜問題1000問(之八) (轉)Oracle
- 一個問題:六位八段數碼管(Verilog)
- js解八皇后問題程式碼例項JS
- 每天刷個演算法題20160526:BFS解決八數碼問題(九宮格問題)演算法
- 八皇后問題的錯誤程式碼示範
- 洛谷八皇后問題
- 八皇后問題python解法Python
- 八皇后問題自我總結
- js使用遞迴回溯法解八皇后問題程式碼分享JS遞迴
- 八皇后問題分析和實現
- 面試程式碼題(vivo)數位之積面試
- HDU 1043——八數碼的多種解題思路(持續更新)
- AI數學基礎之:P、NP、NPC問題AI
- orange人工智慧迴歸問題人工智慧
- orange人工智慧分類問題人工智慧
- request的get和post引數亂碼問題
- 解決Url帶中文引數亂碼問題
- 從八皇后問題到回溯演算法演算法
- iOS八種記憶體洩漏問題iOS記憶體
- 八成Java開發者解答不了的問題Java
- Go 之基礎速學 (八) 方法當中的返回值問題(小白篇)Go
- 人工智慧學會主席:中國人工智慧研究存在問題人工智慧
- python編碼問題之”encode”&”decode”Python
- 讓人工智慧之“光”照亮數字世界人工智慧
- 雙模數問題 題解
- js解決url傳引數中文亂碼問題JS
- ACM 眾數問題ACM
- SQL星期數問題SQL
- Goldegate 追數問題Go
- 眾數問題分析
- 組合數問題
- 回溯法(排列樹)解決八(N)皇后問題
- 解決八種Linux硬碟問題的技巧Linux硬碟
- 企業資訊化的八個策略問題 (轉)
- "走出專案管理的泥沼”之“人員管理”話題之八專案管理