摘要:近日來,人工智能成為科技領域搜索熱詞,無論是從人機大戰的新聞來看,還是從新提出的深度學習理論來分析,我們可以可以清晰的預見,人工智能即將騰飛。
人工智能,顧名思義,就是模擬人類思考模式的超級算法系統,學習能力和推理能力是其核心內容。舉個簡單的例子,“機器學習(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表,選擇最有希望的節點加以擴展
用來加速搜索過程的有關問題領域的特征信息。包括:
用於決定要擴展的下一個節點的信息;
在擴展一個節點時,用於決定要生成哪一個或哪幾個后繼節點的信息;
用於決定某些應該從搜索樹中拋棄或修剪的節點的信息;
使用啟發式信息指導的搜索過程稱為啟發式搜索.
用來估算節點處於最佳求解路徑上的希望程度的函數
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值越大,啟發功能越強, 搜索效率越高.特別地
搜索僅沿最佳路徑進行, 效率最高.
(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