A*算法解决八数码问题 Java语言实现

0X00  定义

  首先要明确一下什么是A*算法和八数码问题?

  A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法也是一种启发性的算法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快。启发中的估价是用估价函数表示的,如: 

f(n) = g(n) + h(n) 

其中f(n) 是节点n的估价函数,g(n)实在状态空间中从初始节点到n节点的实际代价,h(n)是从n到目 标节点最佳路径的估计代价。其中最重要的是h(n)函数,要求

h(n)<h'(n) 

其中h'(n)为实际中当前状态要到达目标状态的步骤数。

  八数码问题就是在一个3*3的矩阵中摆放1-8一共八个数字,还有一个空余的位置用于移动来改变当前的位置来达到最终的状态。如下图

0X01  分析八数码问题

  首先我们要简化一下八数码问题,我们移动数字就是相当于移动空格。这样我们就将问题简化为空格的移动,空格移动的状态只有4种:上、下、左、右。然而在八数码问题中并不是每次空格的移动都有四种状态,我们要判断在当前位置也移动的状态才能移动,我们还要去掉一种状态就是当前状态的父状态,因为如果我们移动到父状态则相当于回退了一步。

  然后,我们要关心的就是给定的初始化状态是否能够通过移动而达到目标状态。这就涉及到了数学问题,就是如果初始状态和目标状态的逆序值同为奇数或同为偶数则可以通过有限次数的移动到达目标状态,否则无解。

  既然我们已经清楚了空格移动的方式,我们讨论一下空格的几种移动的可能方式: 

对应的状态如图所示。

0X02 算法的实现

  A*算法的实现有一个具体的流程图:

 

我们使用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

 

posted @ 2016-10-24 20:04  北林  阅读(18710)  评论(0编辑  收藏  举报