八数码问题

 摘要:近日来,人工智能成为科技领域搜索热词,无论是从人机大战的新闻来看,还是从新提出的深度学习理论来分析,我们可以可以清晰的预见,人工智能即将腾飞。

  人工智能,顾名思义,就是模拟人类思考模式的超级算法系统,学习能力和推理能力是其核心内容。举个简单的例子,“机器学习(MachineLearning)”就是人工智能领域里很有前途的课题,其主要内容是利用大数据训练程序,让它们找到一些可遵循的规律,并且让程序本身大胆的预测结果。在这个过程中搜索策略变的尤为关键。本文主要论述计算机科学与技术专业大三下专业课《人工智能》第二个实验算法。

关键字:人工智能,搜索问题,启发式搜索

Eight digital problem 

Abstract: in recent days, the artificial intelligence search words become areas of science and technology, whether from the point of man-machine war news, or the depth of the new proposed learning theory to the analysis, we can clearly foresee, artificial intelligence is about to take off.

 

  Artificial intelligence, as the name implies, is to simulate human thinking mode of super algorithm system, learning ability and reasoning ability is the core content. A simple example, the "machine learning (MachineLearning)" is the field of artificial intelligence is a promising subject, its main content is to use big data training program, let them find some follow rules, and make bold prediction to the program itself. In the process of the search strategy is particularly critical. This paper mainly discusses the computer science and technology under the junior in professional course "artificial intelligence".

 

Keywords: artificial intelligence, search problems, heuristic search

1,问题重述 
  3×3九宫棋盘,放置数码为1 -8的8个棋牌,剩下一个空格,只能通过棋牌向空格的移动来改变棋盘的布局。

要求:根据给定初始布局(即初始状态)和目标布局(即目标状态),如何移动棋牌才能从初始布局到达目标布局,找到合法的走步序列。
             图片

2,问题分析
  对于八数码问题的解决,首先要考虑是否有答案。每一个状态可认为是一个1×9的矩阵,问题即通过矩阵的变换,是否可以变换为目标状态对应的矩阵?由数学知识可知,可计算这两个有序数列的逆序值,如果两者都是偶数或奇数,则可通过变换到达,否则,这两个状态不可达。这样,就可以在具体解决问题之前判断出问题是否可解,从而可以避免不必要的搜索。
  如果初始状态可以到达目标状态,那么采取什么样的方法呢?
  常用的状态空间搜索有深度优先和广度优先。广度优先是从初始状态一层一层向下找,直到找到目标为止。深度优先是按照一定的顺序前查找完一个分支,再查找另一个分支,以至找到目标为止。广度和深度优先搜索有一个很大的缺陷就是他们都是在一个给定的状态空间中穷举。这在状态空间不大的情况下是很合适的算法,可是当状态空间十分大,且不预测的情况下就不可取了。他的效率实在太低,甚至不可完成。由于八数码问题状态空间共有9!个状态,对于八数码问题如果选定了初始状态和目标状态,有9!/2个状态要搜索,考虑到时间和空间的限制,在这里采用A*算法作为搜索策略。在这里就要用到启发式搜索
  启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无畏的搜索路径,提到了效率。在启发式搜索中,对位置的估价是十分重要的。采用了不同的估价可以有不同的效果。
  启发中的估价是用估价函数表示的,如:f(n) = g(n) +h(n)其中f(n) 是节点n的估价函数,g(n)是在状态空间中从初始节点到n节点的实际代价,h(n)是从n到目标节点最佳路径的估计代价。 在此八数码问题中,显然g(n)就是从初始状态变换到当前状态所移动的步数,估计函数f(n)我们就可采用当前状态各个数字牌不在目标状态未知的个数,即错位数。 

  2.1使用宽度优先搜索方法解决该问题
   为问题状态的表示建立数据结构:
 (1)3×3的一个矩阵,矩阵元素S ij∈{0,1,…,8};其中1≤i,j≤3,
 (2)数字0指示空格,
 (3)数字1 - 8指示相应棋牌。
 (4)制定操作算子集:

   直观方法——为每个棋牌制定一套可能的走步:左、上、右、下四种移动。这样就需32个操作算子。

   简易方法——仅为空格制定这4种走步,因为只有紧靠空格的棋牌才能移动。

   空格移动的唯一约束是不能移出棋盘。  
   图片 
  
  2.2使用深度优先搜索解决该问题
  首先扩展最新产生的(即最深的)节点。防止搜索过程沿着无益的路径扩展下去,往往给出一个节点扩展的最大深度——深度界限。与宽度优先搜索算法最根本的不同在于:将扩展的后继节点放在OPEN表的前端。 
  
  图片

  图片

2.3使用启发式搜索
  特点:重排OPEN表,选择最有希望的节点加以扩展

  种类:有序搜索(A算法)、A*算法等

用来加速搜索过程的有关问题领域的特征信息。包括:
用于决定要扩展的下一个节点的信息;
在扩展一个节点时,用于决定要生成哪一个或哪几个后继节点的信息;
用于决定某些应该从搜索树中抛弃或修剪的节点的信息;
使用启发式信息指导的搜索过程称为启发式搜索.
用来估算节点处于最佳求解路径上的希望程度的函数

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

n ——搜索图中的某个当前被扩展的节点;

f(n) ——从初始状态节点s, 经由节点n到达目标节点ng,估计的最小路径代价;

g(n) ——从s到n 的实际路径代价;

 h(n)——从n到ng,估计的最小路径代价。
   

   估价函数:f(n)=d(n)+w(n) 
        其中:d(n)为n的深度 w(n)为不在位的棋子数
3,求解过程
  不管哪种搜索,都统一用这样的形式表示:搜索的对象是一个图,它面向一个问题,不一定有明确的存储形式,但它里面的一个结点都有可能是一个解(可行解),搜索的目的有两个方面,或者求可行解,或者从可行解集中求最优解。
  搜索算法可分为两大类:无信息的搜索算法和有信息的搜索算法。无信息的搜索又称盲目搜索,其特点是只要问题状态可以形式化表示,原则上就可用使用无信息的搜索,无信息搜索有如下常见的几种搜索策略:广度优先搜索、代价一致搜索、深度优先搜索、深度有限搜索、迭代深入优先搜索、双向搜索。我们说DFS和BFS都是蛮力搜索,因为它们在搜索到一个结点时,在展开它的后续结点时,是对它们没有任何‘认识’的,它认为它的孩子们都是一样的‘优秀’,但事实并非如此,后续结点是有好有坏的。好,就是说它离目标结点‘近’,如果优先处理它,就会更快的找到目标结点,从而整体上提高搜索性能。
  为了改善上面的算法,我们需要对展开后续结点时对子结点有所了解,这里需要一个估值函数,估值函数就是评价函数,它用来评价子结点的好坏,因为准确评价是不可能的,所以称为估值。这就是我们所谓的有信息搜索。如果估值函数只考虑结点的某种性能上的价值,而不考虑深度,比较有名的就是有序搜索(Ordered-Search),它着重看好能否找出解,而不看解离起始结点的距离(深度)。如果估值函数考虑了深度,或者是带权距离(从起始结点到目标结点的距离加权和),那就是A*如果不考虑深度,就是说不要求最少步数,移动一步就相当于向后多展开一层结点,深度多算一层,如果要求最少步数,那就需要用A*。简单的来说A*就是将估值函数分成两个部分,一个部分是路径价值,另一个部分是一般性启发价值,合在一起算估整个结点的价值,

考虑到八数码问题的特点,在本实验中使用A*算法求解。A*搜索是一种效的搜索算法,它把到达节点的耗散g(n)和从该节点到目标节点的消耗h(n)结合起来对节点进行评价:f(n)=g(n)+h(n)。当h(n)是可采纳时,使用Tree-Search的A*算法将是最优的。

      图片     

      图片

  A*算法,1 在GRAPHSEARCH过程中,如果第8步的重排OPEN表是依据f(n)=g(n)+h(n)进行的,则称该过程为A算法。在A算法中,如果对所有的n存在h(n)≤h*(n),则称h(n)为h*(n)的下界,它表示某种偏于保守的估计。采用h*(n)的下界h(n)为启发函数的A算法,称为A*算法。当h=0时,A*算法就变为有序搜索算法。
  在A算法中,如果满足条件:

  (1) g(n)是对g*(n)的估计,且g(n)>0;

  (2) h(n)是h*(n)的下界,即对任意节点n均有0≤h(n)≤h*(n)则A算法称为A*算法
  A*算法的可纳性,对任一个图,存在从S到目标的路径,如果一个搜索算法总是结束在一条从S到目标的最佳路径上,则称此算法是可采纳的。算法A*保证只要最短路径存在,就一定能找出这条路径,所以算法A*是可纳的。
  估价函数:f(n)=d(n)+w(n) 

  其中:d(n)为n的深度 w(n)为不在位的棋子数

      取h(n)=w(n),则有w(n)≤h*(n),h(n)满足A*算法的限制条件。 
  在八数码难题中, 令估价函数

    f(n)=d(n)+p(n)

  启发函数h(n)=p(n),p(n)为不在位的棋子与其目标位置的距离之和,则有p(n)≤h*(n),满足A*算法的限制条件。  
  w(n)——不在位的棋子数,不够贴切,错误选用节点加以扩展。
  更接近于h*(n)的h(n),其值是节点n与目标状态节点相比较,每个错位棋子在假设不受阻拦的情况下,移动到目标状态相应位置所需走步的总和。(n)比w(n)更接近于h*(n),因为p(n)不仅考虑了错位因素,还考虑了错位的距离(移动次数)。
  说明h值越大,启发功能越强, 搜索效率越高.特别地

  (1)h(n)=h*(n)

    搜索仅沿最佳路径进行, 效率最高.
 (2)h(n)=0

    无启发信息, 盲目搜索, 效率低.
 (3)h(n)>h*(n)

    是一般的A算法,效率高, 但不能保证找到最佳路径. 有时为求解难题取h(n)>h*(n), 以提高效率. 
  
             图片

      图片
4,程序设计
 

  1 #include<iostream>
  2 #include<cstdlib>
  3 #include<ctime>
  4 using namespace std;
  5 
  6 class EightPuzzle
  7 {
  8 private:
  9     int num[9];
 10     int malposition;
 11     int depth;
 12     int evaluation;
 13 public:
 14     EightPuzzle *parent;
 15     EightPuzzle *leaf_last;
 16     EightPuzzle *leaf_next;
 17 public:
 18     EightPuzzle(int *num_input);
 19     void init(int *target);
 20     void setNum(int num[]);
 21     int *getNum();
 22     void getNum(int *num);
 23     int getMalposition()
 24     {
 25         return this->malposition;
 26     }
 27     int getDepth()
 28     {
 29         return this->depth;
 30     }
 31     int getEvaluation()
 32     {
 33         return this->evaluation;
 34     }
 35     void print();
 36     bool solvable(int *target);
 37     bool find_target(int *target);
 38     EightPuzzle& operator=(EightPuzzle& eightPuzzle);
 39     EightPuzzle& operator=(int other_num[9]);
 40     bool operator==(EightPuzzle& eigthPuzzle);
 41     bool operator==(int other_num[9]);
 42 };
 43 
 44 EightPuzzle::EightPuzzle(int *num_input)
 45 {
 46     int ii;
 47     for (ii = 0; ii<9; ii++)
 48     {
 49         num[ii] = num_input[ii];
 50     }
 51     this->leaf_last = NULL;
 52     this->leaf_next = NULL;
 53     this->parent = NULL;
 54 }
 55 
 56 EightPuzzle& EightPuzzle::operator=(EightPuzzle& eightPuzzle)
 57 {
 58     int ii;
 59     for (ii = 0; ii < 9; ii++)
 60     {
 61         this->num[ii] = eightPuzzle.getNum()[ii];
 62     }
 63     this->malposition = eightPuzzle.getMalposition();
 64     this->depth = eightPuzzle.getDepth() + 1;
 65     this->evaluation = this->malposition + this->depth;
 66     return *this;
 67 }
 68 EightPuzzle& EightPuzzle::operator=(int other_num[9])
 69 {
 70     int ii;
 71     for (ii = 0; ii < 9; ii++)
 72     {
 73         num[ii] = other_num[ii];
 74     }
 75     return *this;
 76 }
 77 bool EightPuzzle::operator==(EightPuzzle& eightPuzzle)
 78 {
 79     int match = 1;
 80     int ii;
 81     for (ii = 0; ii < 9; ii++)
 82     {
 83         if (this->num[ii] != eightPuzzle.getNum()[ii])
 84         {
 85             match = 0;
 86             break;
 87         }
 88     }
 89     if (match == 0)
 90         return false;
 91     else
 92         return true;
 93 }
 94 bool EightPuzzle::operator==(int other_num[9])
 95 {
 96     int match = 1;
 97     int ii;
 98     for (ii = 0; ii < 9; ii++)
 99     {
100         if (this->num[ii] != other_num[ii])
101         {
102             match = 0;
103             break;
104         }
105     }
106     if (match == 0)
107         return false;
108     else
109         return true;
110 }
111 
112 void EightPuzzle::init(int *target)
113 {
114     int ii;
115     int temp = 0;
116     for (ii = 0; ii < 9; ii++)
117     {
118         if (num[ii] != target[ii])
119         {
120             temp++;
121         }
122     }
123     this->malposition = temp;
124     if (this->parent == NULL)
125     {
126         this->depth = 0;
127     }
128     else
129     {
130         this->depth = this->parent->depth + 1;
131     }
132     this->evaluation = this->malposition + this->depth;
133 }
134 
135 void EightPuzzle::setNum(int num[])
136 {
137     int ii;
138     for (ii = 0; ii < 9; ii++)
139     {
140         this->num[ii] = num[ii];
141     }
142 }
143 
144 int *EightPuzzle::getNum()
145 {
146     return this->num;
147 }
148 
149 void EightPuzzle::getNum(int *num)
150 {
151     int ii;
152     for (ii = 0; ii < 9; ii++)
153     {
154         num[ii] = this->num[ii];
155     }
156 }
157 
158 bool EightPuzzle::solvable(int *target)
159 {
160     int ii, ij;
161     int count_num=0, count_target=0;
162     for (ii = 0; ii < 9; ii++)
163     {
164         for (ij = 0; ij < ii; ij++)
165         {
166             if ((this->num[ij] < this->num[ii]) && (this->num[ij] != 0))
167             {
168                 count_num++;
169             }
170             if (target[ij] < target[ii] && target[ij] != 0)
171             {
172                 count_target++;
173             }
174         }
175     }
176     if ((count_num + count_target) % 2 == 0)
177     {
178         return true;
179     }
180     else
181     {
182         return false;
183     }
184 }
185 
186 bool EightPuzzle::find_target(int *target)
187 {
188     int ii;
189     for (ii = 0; ii < 9; ii++)
190     {
191         if (this->num[ii] != target[ii])
192         {
193             break;
194         }
195     }
196     if (ii == 9)
197     {
198         return true;
199     }
200     else
201     {
202         return false;
203     }
204 }
205 
206 bool move_up(int *num)
207 {
208     int ii;
209     for (ii = 0; ii < 9; ii++)
210     {
211         if (num[ii] == 0)
212         {
213             break;
214         }
215     }
216     if (ii < 3)
217     {
218         return false;
219     }
220     else
221     {
222         num[ii] = num[ii - 3];
223         num[ii - 3] = 0;
224     }
225     return true;
226 }
227 
228 bool move_down(int *num)
229 {
230     int ii;
231     for (ii = 0; ii < 9; ii++)
232     {
233         if (num[ii] == 0)
234         {
235             break;
236         }
237     }
238     if (ii > 5)
239     {
240         return 0;
241     }
242     else
243     {
244         num[ii] = num[ii + 3];
245         num[ii + 3] = 0;
246     }
247     return true;
248 }
249 
250 bool move_left(int *num)
251 {
252     int ii;
253     for (ii = 0; ii < 9; ii++)
254     {
255         if (num[ii] == 0)
256         {
257             break;
258         }
259     }
260     if (ii == 0 || ii == 3 || ii == 6)
261     {
262         return false;
263     }
264     else
265     {
266         num[ii] = num[ii - 1];
267         num[ii - 1] = 0;
268     }
269     return true;
270 }
271 
272 bool move_right(int *num)
273 {
274     int ii;
275     for (ii = 0; ii < 9; ii++)
276     {
277         if (num[ii] == 0)
278         {
279             break;
280         }
281     }
282     if (ii == 2 || ii == 5 || ii == 8)
283     {
284         return false;
285     }
286     else
287     {
288         num[ii] = num[ii + 1];
289         num[ii + 1] = 0;
290     }
291     return true;
292 }
293 
294 void EightPuzzle::print()
295 {
296     int ii;
297     for (ii = 0; ii<9; ii++)
298     {
299         if ((ii + 1) % 3 != 0)
300         {
301             cout << num[ii] << ",";
302         }
303         else
304         {
305             cout << num[ii] << endl;
306         }
307     }
308 }
309 
310 bool existed(int *num, EightPuzzle *start)
311 {
312     EightPuzzle *temp;
313     for (temp = start; temp != NULL; temp = temp->parent)
314     {
315         if (*temp == num)
316         {
317             return true;
318         }
319     }
320     return false;
321 }
322 
323 EightPuzzle *best_route(EightPuzzle *start,EightPuzzle *target)
324 {
325     EightPuzzle *temp, *best;
326     temp = best = start;
327     start->init(target->getNum());
328     int min = start->getEvaluation();
329     for (temp = start; temp != NULL; temp = temp->leaf_next)
330     {
331         if (min > temp->getEvaluation())
332         {
333             best = temp;
334             min = temp->getEvaluation();
335         }
336     }
337     return best;
338 }
339 
340 void print_route(EightPuzzle *best,int list_length)
341 {
342     int step = 0;
343     EightPuzzle *temp;
344     for (temp = best->parent; temp != NULL; temp = temp->parent)
345     {
346         cout << endl;
347         temp->print();
348         step++;
349     }
350     cout << endl << "The total steps is " << step << "." << endl;
351     cout << endl << "The memory cost is " << list_length << "." << endl;
352     return;
353 }
354 
355 void proceeding(EightPuzzle &start, EightPuzzle &target)
356 {
357     if (!start.solvable(target.getNum()))
358     {
359         cout <<endl<< "The serious number you input can't be solvable!" << endl;
360         return;
361     }
362     EightPuzzle *best = &start;
363     EightPuzzle *list = &start;
364     EightPuzzle *apply,*temp;
365     int num[9],list_length=0;
366     while (best != NULL)
367     {
368         best = best_route(list,&target);
369         if (best->find_target(target.getNum()))
370         {
371             print_route(best,list_length);
372             return;
373         }
374         temp = best->leaf_last;
375         best->getNum(num);
376         if (move_up(num) && !existed(num, best))
377         {
378             apply = new EightPuzzle(num);
379             apply->parent = best;
380             apply->init(target.getNum());
381             apply->leaf_last = temp;
382             if (temp == NULL)
383             {
384                 list = apply;
385             }
386             else
387             {
388                 temp->leaf_next = apply;
389             }
390             temp = apply;
391             list_length++;
392         }
393         best->getNum(num);
394         if (move_down(num) && !existed(num, best))
395         {
396             apply = new EightPuzzle(num);
397             apply->parent = best;
398             apply->init(target.getNum());
399             apply->leaf_last = temp;
400             if (temp == NULL)
401             {
402                 list = apply;
403             }
404             else
405             {
406                 temp->leaf_next = apply;
407             }
408             temp = apply;
409             list_length++;
410         }
411         best->getNum(num);
412         if (move_left(num) && !existed(num, best))
413         {
414             apply = new EightPuzzle(num);
415             apply->parent = best;
416             apply->init(target.getNum());
417             apply->leaf_last = temp;
418             if (temp == NULL)
419             {
420                 list = apply;
421             }
422             else
423             {
424                 temp->leaf_next = apply;
425             }
426             temp = apply;
427             list_length++;
428         }
429         best->getNum(num);
430         if (move_right(num) && !existed(num, best))
431         {
432             apply = new EightPuzzle(num);
433             apply->parent = best;
434             apply->init(target.getNum());
435             apply->leaf_last = temp;
436             if (temp == NULL)
437             {
438                 list = apply;
439             }
440             else
441             {
442                 temp->leaf_next = apply;
443             }
444             temp = apply;
445             list_length++;
446         }
447         temp->leaf_next = best->leaf_next;
448         if (best->leaf_next != NULL)
449         {
450             best->leaf_next->leaf_last = temp;
451         }
452         best->leaf_next = best->leaf_last = NULL;
453     }
454 }
455 
456 void input(int num_init[])
457 {
458     int ii, ij;
459     cout << "Please input the initial state of the eight puzzle:" << endl;
460     cout << "(0 for the blank)" << endl << endl;
461     for (ii = 0; ii<9; ii++)
462     {
463         cin >> num_init[ii];
464         if (num_init[ii]<0 || num_init[ii]>8)
465         {
466             cout << "Wrong number! Please input again:(0-8)" << endl;
467             ii--;
468         }
469         for (ij = 0; ij<ii; ij++)
470         {
471             if (num_init[ii] == num_init[ij])
472             {
473                 cout << "The number you inputed is duplicated! Try again:" << endl;
474                 ii--;
475             }
476         }
477     }
478 }
479 
480 int main(int argc, char **argv)
481 {
482     double time;
483     clock_t START, FINISH;
484     int num_init[9];
485     input(num_init);
486     EightPuzzle *start = new EightPuzzle(num_init);
487     int num_target[9] = { 1,2,3,8,0,4,7,6,5 };
488     EightPuzzle *target = new EightPuzzle(num_target);
489     cout << "The initial serial number is:" << endl;
490     start->print();
491     cout << "The target serial number is:" << endl;
492     target->print();
493     START = clock();
494     proceeding(*start, *target);
495     FINISH = clock();
496     time = (double)(FINISH - START) * 1000 / CLOCKS_PER_SEC;
497     cout << endl << "The total time cost to solve the puzzle is: ";
498     cout<< time <<" millisecond."<< endl << endl;
499     system("pause");
500     return 0;
501 }
502 
503 //216408753->18steps

 

posted @ 2016-05-12 15:21  光何  阅读(48399)  评论(0编辑  收藏  举报