八數碼問題


 摘要:近日來,人工智能成為科技領域搜索熱詞,無論是從人機大戰的新聞來看,還是從新提出的深度學習理論來分析,我們可以可以清晰的預見,人工智能即將騰飛。

  人工智能,顧名思義,就是模擬人類思考模式的超級算法系統,學習能力和推理能力是其核心內容。舉個簡單的例子,“機器學習(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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM