1. 程式人生 > >A*演算法解決八數碼問題

A*演算法解決八數碼問題

問題描述

1.1什麼是八數碼問題

八數碼遊戲包括一個33的棋盤,棋盤上擺放著8個數字的棋子,留下一個空位。與空位相鄰的棋子可以滑動到空位中。遊戲的目的是要達到一個特定的目標狀態。標註的形式化如下:

1

2

3

4

5

6

7

8

1.2問題的搜尋形式描述

狀態:狀態描述了8個棋子和空位在棋盤的9個方格上的分佈。

初始狀態:任何狀態都可以被指定為初始狀態。

操作符:用來產生4個行動(上下左右移動)。

目標測試:用來檢測狀態是否能匹配上圖的目標佈局。

路徑費用函式:每一步的費用為1,因此整個路徑的費用是路徑中的步數。

現在任意給定一個初始狀態,要求找到一種搜尋策略,用盡可能少的步數得到上圖的目標狀態。

1.3解決方案介紹

1.3.1 演算法思想

估價函式是搜尋特性的一種數學表示,是指從問題樹根節點到達目標節點所要耗費的全部代價的一種估算,記為f(n)。估價函式通常由兩部分組成,其數學表示式為

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

其中f(n) 是節點n從初始點到目標點的估價函式,g(n) 是在狀態空間中從初始節點到n節點的實際代價,h(n)是從n到目標節點最佳路徑的估計代價。保證找到最短路徑(最優解)的條件,關鍵在於估價函式h(n)的選取。估價值h(n)<= n到目標節點的距離實際值,這種情況下,搜尋的點數多,搜尋範圍大,效率低。但能得到最優解。如果估價值>實際值搜尋的點數少,搜尋範圍小,效率高,但不能保證得到最優解。

搜尋中利用啟發式資訊,對當前未擴充套件結點根據設定的估價函式值選取離目標最近的結點進行擴充套件,從而縮小搜尋空間,更快的得到最優解,提高效率。

1.3.2 啟發函式

     進一步考慮當前結點與目標結點的距離資訊,令啟發函式h ( n )為當前8個數字位與目標結點對應數字位距離和(不考慮中間路徑),且對於目標狀態有 h ( t ) = 0,對於結點mm的子結點) 有h ( m ) – h ( n ) <= 1 = Cost ( m, n ) 滿足單調限制條件。

2演算法介紹

2.1 A*演算法的一般介紹

A*A-Star)演算法是一種靜態路網中求解最短路最有效的方法。

A star演算法在靜態路網中的應用

對於幾何路網來說,可以取兩節點間歐幾理德距離(直線距離)做為估價值,即f=g(n)+sqrt((dx-nx)*(dx-nx)+(dy-ny)*(dy-ny));這樣估價函式fg值一定的情況下,會或多或少的受估價值h的制約,節點距目標點近,h值小,f值相對就小,能保證最短路的搜尋向終點的方向進行。明顯優於盲目搜尋策略。

2.2演算法虛擬碼

  建立兩個表,OPEN表儲存所有已生成而未考察的節點,CLOSED表中記錄已訪問過的節點。算起點的估價值,將起點放入OPEN

  while(OPEN!=NULL)

  {

  從OPEN表中取估價值f最小的節點n;

if(n節點==目標節點)

{break;}

  for(當前節點的每個子節點X)

   {

  算X的估價值;

  if(X in OPEN)

    {

if( X的估價值小於OPEN表的估價值 )

{n設定為X的父親;

  更新OPEN表中的估價值; //取最小路徑的估價值}

    }

if(X inCLOSE) 

{

if( X的估價值小於CLOSE表的估價值 )

{n設定為X的父親;

  更新CLOSE表中的估價值;

  把X節點放入OPEN //取最小路徑的估價值}

    }

if(X not inboth)

{n設定為X的父親;

  求X的估價值;

  並將X插入OPEN表中; //還沒有排序}

   }//end for

  將n節點插入CLOSE表中;

  按照估價值將OPEN表中的節點排序; //實際上是比較OPEN表內節點f的大小,從最小路徑的節點向下進行。

  }//end while(OPEN!=NULL)

儲存路徑,即 從終點開始,每個節點沿著父節點移動直至起點,這就是你的路徑;

判斷有無解問題:根據逆序數直接判斷有無解,對於一個八數碼,依次排列之後,每次是將空位和相鄰位進行調換,研究後會發現,每次調換,逆序數增幅都為偶數,也就是不改變奇偶性,所以初始和目標狀態的逆序數的奇偶性相同。

3演算法實現

3.1實驗環境與問題規模

對於8數碼問題,每個結點有8個數字和一個空格,可以將空格看成0,那麼一共有9個數字,32位的int可以表示2* 109 ,可以用一個整數表示一個結點對應的資訊。計算一個整數中0(即空格)的位置比較耗時間,用一個整數儲存當前結點0的位置,還要儲存對應的 g , h 值以及該結點由哪個結點擴充套件來的資訊。本實驗用C++編寫源程式,環境選用Visual Studio 2005

程式採用文字輸入輸出,輸入檔案為astar.inA*演算法輸出檔案為astar.out,可以用記事本開啟。輸入格式為一個測試用例由兩個中間由一空行隔開的8數碼格局組成,輸出為對應測試用例的走法路徑及相關統計資訊,程式假定輸入資料符合要求,未做檢查。

Astar.in:

2 0 3  //初態

1 8 4

7 6 5

1 2 3 // 終態

8 0 4

7 6 5

3.2資料結構

3.2.1 open表的資料結構表示
      考慮對open表的操作,每次需要得到所有待擴充套件結點中 值最小的那個結點,用堆進行實現,可以達到O ( log ( heapSize ) ) 時間複雜度。

3.2.2 closed表的資料結構表示
      closed表儲存已擴充套件的結點間的擴充套件關係,主要用於輸出路徑。考慮結點擴充套件的操作,設待擴充套件的結點為m,由它擴充套件生成的結點為n1, n2, … 。結點m擴充套件完成後被放到closed表中,放入後它在closed表中位置不發生變化,可以將n1, n2, …的前驅結點置為mclosed表中的位置,當n1, n2, ..中有結點設為n1被擴充套件放入closed表時,n1的前驅剛好已經儲存好。下面說明closed表中任意一個結點都儲存有它的前驅結點的資訊,考慮closed表中任意一個結點,如果它是初始結點,它沒有前驅結點,如果不是根結點,擴充套件該結點時它的前驅結點已經記錄。從而在closed表中形成擴充套件關係的樹狀結構。因為只需要前驅結點的下標位置,可以用陣列實現,每個結點記錄整數表示的8數碼格局和它的前驅結點的下標,輸出路徑時,根據前驅結點形成到達根結點的鏈條,遞迴輸出即可。

3.2.3 解決結點重複擴充套件問題
      對於一個結點有多種方式到達該結點,這樣就可能多次將它加入open表中,而啟發函式滿足單調限制條件,後來達到該結點的路徑不再是更優的,可以不予考慮。擴充套件某結點時先看該結點是否已經擴充套件過,如果擴充套件過則略過。實現的可以線形遍歷closed表,但效率不高時間複雜度為O ( closedSize),考慮每個結點可以用一個整數標識,用二叉平衡查詢樹可以得到更好的時間複雜度O ( log (closedSize) ) ,程式中用基於紅黑樹思想的set實現。

3.3 實驗結果

輸入資料(0表示空格)

步數

擴充套件結點數

生成結點數

搜尋用時(毫秒)

3 1 2 4 0 5 6 7 8

2

5

11

0

3 1 2 4 7 5 6 8 0

4

17

28

0

3 7 2 8 1 5 4 6 0

無解

1 2 3 4 5 6 7 8 0

22

7943

12019

2266

參考文獻

王勳,凌雲,費玉蓮.2005.人工智慧導論.北京:科學出版社

廣樹建,王鈺淇.2008.新編C/C++程式設計教程.廣州:華南理工大學出版社

王文傑,史忠植.2007.人工智慧原理輔導與練習.北京:清華大學出版社出

附錄—原始碼及其註釋

原始碼及測試資料
/*
        演算法: A*
        是否最優解:是
        啟發函式:   每一個數字位與目標中該數字位的距離,滿足單調限制。說明:A*演算法是啟發式搜尋演算法,搜尋時充分利用當前狀態距目標距離遠近的啟發資訊,選取當前未擴充套件結點中估價函式最小的進行擴充套件,生成結點數少,搜尋空間較小,實現稍複雜,
        備註:程式未對輸入資料進行檢查
*/

#pragma warning(disable:4786) 
#include <algorithm> 
#include <cstdio> 
#include <set> 
#include <utility> 
#include <ctime> 
#include <cassert> 
#include <cstring> 
#include <iostream>
using namespace std;

/*item記錄搜尋空間中一個結點         
state 記錄用整數形式表示的8數碼格局          
blank 記錄當前空格位置,主要用於程式優化,
擴充套件時可不必在尋找空格位置         
g, h  對應g(n), h(n)          
pre   記錄當前結點由哪個結點擴充套件而來 */
struct item  
{          
	int state;          
	int blank;         
	int g;         
	int h;           
	int pre; 
};

const int MAXSTEPS = 100000; 
const int MAXCHAR = 100; 
char buf[MAXCHAR][MAXCHAR]; //open表 
item open[MAXSTEPS]; 
//vector<item> open;
int steps = 0;

//closed表,已查詢狀態只要知道該狀態以及它由哪個結點擴充套件而來即可,用於輸出路徑 
//每次只需得到對應f值最小的待擴充套件結點,用堆實現提高效率
pair<int, int> closed[MAXSTEPS];
//讀入,將8數碼矩陣格局轉換為整數表示 

bool read(pair<int,int> &state) 
{          
	if (!gets(buf[0]))                 
		return false;         
	if (!gets(buf[1]))                 
		return false;         
	if (!gets(buf[2]))                 
		return false; 

	//cout << strlen(buf[0]) << ' ' << strlen(buf[1]) << ' ' << strlen(buf[2]) << endl;
	assert(strlen(buf[0]) == 5 && strlen(buf[1]) == 5 && strlen(buf[2]) == 5);
	// astar.in中的每行資料長度必須為5
	state.first = 0;
	for (int i = 0, p = 1; i < 3; ++i)         
	{                  
		for (int j = 0; j < 6; j += 2)                  
		{                          
			if (buf[i][j] == '0')                                  
				state.second = i * 3 + j / 2;     // state.second為0(空格)在節點中的位置                    
			else                                  
				state.first += p * (buf[i][j] - '0');                
			p *= 10;                 
		}         
	}

	/* 若初試節點為:   
	1 2 3
	8 0 4
	7 6 5
	則state.first為567408321,state.second為4
	*/
	return true;
}

//計算當前結點距目標的距離 
int calculate(int current, int target)  // return h=the sum of distances each block have to move to the right position,這裡的each block不包括空格
{          
	int c[9], t[9];         
	int i, cnt = 0;          
	for (i = 0; i < 9; ++i)         
	{                  
		c[current % 10] = t[target % 10] = i;                 
		current /= 10;                
		target /= 10;         
	}  

	for (i = 1; i < 9; ++i)                  
		cnt += abs(c[i] / 3 - t[i] / 3) + abs(c[i] % 3 - t[i] % 3);         

	return cnt; 
}

//open表中結點間選擇時的規則 f(n) = g(n) + h(n)

class cmp 
{ 
public: inline bool operator()(item a, item b)         
		{                  
			return a.g + a.h > b.g + b.h;         
		} 
}; 

//將整數形式表示轉換為矩陣表示輸出
void pr(int state) 
{          
	memset(buf, ' ', sizeof(buf));         
	for (int i = 0; i < 3; ++i)         
	{                  
		for (int j = 0; j < 6; j += 2)                 
		{                          
			if (state % 10)
				buf[i][j] = state % 10 + '0';                         
			state /= 10;                 
		}       

		buf[i][5] = '\0';                 
		puts(buf[i]);
	}
}

//用於判斷當前空格是否可以向對應方向移動
inline bool suit(int a, int b)  //空格移動後的座標為(a,b)
{          
	return (a >= 0 && a < 3 && b >= 0 && b < 3); 
} 


//遞迴輸出搜尋路徑
void path(int index) 
{          
	if (index == 0)         
	{                  
		pr(closed[index].first);                 
		puts("");                 
		return;         
	}          
	path(closed[index].second);         
	pr(closed[index].first); //將整數形式表示轉換為矩陣表示輸出         
	puts("");         
	++steps; 
}

int getNixuNum( int state ) //求節點的逆序對數
{
	int sum = 0;
	int result[9];
	memset( result, 0, sizeof(result) );
	//cout << result[8] << result[7] << endl;

	char buf[10];
	itoa( state, buf, 10 );
	//cout << buf << endl;
	int k = 0;
	while( buf[k] != '\0' )
	{
		result[9-k-1] = buf[k] - '0';
		k++;
	}
	
	for( int i = 0; i < 9; i++ )
	{
		for( int j = i + 1; j < 9; j++ )
		{
			if( result[i] && result[j] && result[i] > result[j] )
			{
				sum++;
			}
		}
	}
	return sum; //返回3*3方格陣列的逆序對數
}

int main() 
{    
	//cout << getNixuNum(87654321);
	//open.resize(MAXSTEPS);
	unsigned int t1 = clock();    
	//cout << open.size() << endl;
	if( freopen("astar.in", "r", stdin) == NULL ) 
	{
		cout << "file not find\n";
		exit(0);
	};    

	freopen("astar2.out", "w", stdout);         
	set<int>states;         
	char tmp[100];          
	int i, x, y, a, b, nx, ny, end, next, index, kase = 0;         
	pair<int,int> start, target;         
	item head;          //4個方向移動時的偏移量          
	const int xtran[4] = {-1, 0, 1, 0};         
	const int ytran[4] = {0, 1, 0, -1};          
	const int p[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};

	while (read(start))  // 讀取初試狀態節點       
	{                  
		unsigned int t2 = clock();                  
		printf("Case %d:\n\n", ++kase);                 
		gets(tmp);                 
		read(target);  // 讀取目標狀態節點          
		gets(tmp); 

		int targetNixuNum = getNixuNum(target.first);
		//若兩者的逆序對數不是同為奇數或同為偶數,則無解
		if( !(getNixuNum(start.first)&1 && targetNixuNum&1 || !(getNixuNum(start.first)&1) && !(targetNixuNum&1)) )
		{
			cout << "無法從初始節點到終態節點\n";
			exit(0);
		}
		//初始化open表,將初始狀態加入
		open[0].state = start.first;                  
		open[0].h = calculate(start.first, target.first); // 計算當前節點到目標節點的估計距離                
		open[0].blank = start.second;                 
		open[0].pre = -1;    // 初始節點無父節點             
		open[0].g = 0;  // 初始節點的g為0               
		index = 0;                  
		states.insert(start.first); // 擴充套件過節點儲存在states中,即出現過的狀態儲存在states中,states為set<int>型別,其中的states中的元素唯一

		//提取open表中f值最小元素放入closed表,並對該結點進行擴充套件
		for (end = 1; end > 0; ++index)   // end為open表中的元素個數,一直迴圈到open表為空              
		{                          
			assert(index < MAXSTEPS);       
			//臨時儲存                         
			head = open[0]; // 由於使用pop_heap函式和push_heap函式,所以open[0]為g+h最小的元素

			//放入closed表記錄當前格局和由哪個結點擴充套件而來(該結點肯定已在closed表中)
			closed[index].first = open[0].state; //放入close表中,表示已經擴充套件完的節點,下面的for迴圈會擴充套件其節點                         
			closed[index].second = open[0].pre; // index表示當前close表中當前擴充套件節點的下標
			//從open表中刪除該結點                          
			pop_heap(open, open + end, cmp());//為algorithm檔案中的函式,第一個引數指定開始位置,第二個指定結束,第三個指定比較函式                         
			--end;      

			//得到結果,遞迴輸出路徑
			if (head.state == target.first)                         
			{                                  
				path(index);                                 
				break;                         
			}

			x = head.blank / 3;                         
			y = head.blank % 3; //空格在3*3方格中的x,y座標
			/*
			    |2 0 3|
			A = |1 8 4|
			    |7 6 5| // 看成3*3的陣列
			則head.blank=1
			x=0,y=1,即空格的在3*3的陣列中下標為(0,1)
			*/
			for (i = 0; i < 4; ++i)                         
			{                                  
				nx = x + xtran[i];                                 
				ny = y + ytran[i];   
				/*
				i=0時:(nx,ny)為當前空格向上移動一格後的座標
				i=1時:(nx,ny)為當前空格向右移動一格後的座標
				i=2時:(nx,ny)為當前空格向下移動一格後的座標
				i=3時:(nx,ny)為當前空格向左移動一格後的座標
				*/
				if (suit(nx, ny)) // 判斷是否能夠移動
				{                                          
					a = head.blank; // 空格當前位置,以上面矩陣A為例,a=1                                       
					b = nx * 3 + ny; // 空格移動後的新位置,開始是能夠向右邊移動,故b=0*3+2=2                                        
					//調換十進位制表示整數對應兩個數字位                                          
					next = head.state + ((head.state % p[a + 1]) / p[a] - (head.state % p[b + 1]) / p[b]) * p[b]  + ((head.state % p[b + 1]) / p[b] - (head.state % p[a + 1]) / p[a]) * p[a];   
					// 如head.state=567481302,空格向右移動一次後,next=567481032,即為移動後的節點
					
					// 判斷能否由當前節點到達目標節點
					if( ( getNixuNum(next)&1 && targetNixuNum&1 ) || ( !(getNixuNum(next)&1) && !(targetNixuNum&1) ) )
					{
						//判斷是否已經擴充套件過,即已經出現過
						if (states.find(next) == states.end()) //沒出現就儲存一下,也存入open表                                        
						{                                                  
							states.insert(next);                                                     
							open[end].pre = index; //擴充套件後的子節點,其父節點為當前擴充套件節點                                                
							open[end].blank = b;                                                 
							open[end].state = next;                                                  
							open[end].h = calculate(next,target.first); 
							open[end].g  = head.g + 1;  
							++end;  //open表中元素加1                                                
							push_heap(open, open + end, cmp());    //壓入堆中                                     
						}           
					}
					                      
				}                         
			}                 
		} 

		if (end <= 0)                          
			puts("No solution");
		else                 
		{                          
			printf("Num of steps: %d\n", steps);                          
			printf("Num of expanded: %d\n", index);                         
			printf("Num of generated: %d\n", index + end);                         
			printf("Time consumed: %d\n\n", clock() - t2);                 
		} 

		states.clear();                 
		steps = 0;         
	}          
	printf("Total time consumed: %d\n", clock() - t1);         
	return 0;
}

測試:

輸入檔案:astar.in

輸出檔案:astar2.out

astar.in檔案內容:

3 1 2
4 0 5
6 7 8

0 1 2
3 4 5
6 7 8

1 2 3
4 5 6
7 8 0

0 1 2
3 4 5
6 7 8

注:上面前兩個3*3矩陣為第一個測試案例,其中第一個3*3為初態節點,第二個3*3為終態節點,後面兩個3*3矩陣為第二個測試案例,

其中第一個3*3為初態節點,第二個3*3為終態節點,各個矩陣之間需要空一行

測試案例1:

astar.in:

3 1 2
4 0 5
6 7 8

0 1 2
3 4 5
6 7 8

astar2.out:

Case 1:

3 1 2
4   5
6 7 8

3 1 2
  4 5
6 7 8

  1 2
3 4 5
6 7 8

Num of steps: 2
Num of expanded: 2
Num of generated: 6
Time consumed: 64

Total time consumed: 92

測試案例2:

astar.in:

3 7 2
8 1 5
4 6 0

0 1 2
3 4 5
6 7 8

astar2.out:

Case 1:

無法從初始節點到終態節點

注:astar.in中每一行資料長度只能是5,0表示空格

測試案例3:

astar.in:

1 2 3
4 5 6
7 8 0

0 1 2
3 4 5
6 7 8

astar2.out:

Case 1:

1 2 3
4 5 6
7 8  

1 2 3
4 5 6
7   8

1 2 3
4   6
7 5 8

1 2 3
4 6  
7 5 8

1 2  
4 6 3
7 5 8

1   2
4 6 3
7 5 8

  1 2
4 6 3
7 5 8

4 1 2
  6 3
7 5 8

4 1 2
6   3
7 5 8

4 1 2
6 3  
7 5 8

4 1  
6 3 2
7 5 8

4   1
6 3 2
7 5 8

4 3 1
6   2
7 5 8

4 3 1
6 5 2
7   8

4 3 1
6 5 2
  7 8

4 3 1
  5 2
6 7 8

  3 1
4 5 2
6 7 8

3   1
4 5 2
6 7 8

3 1  
4 5 2
6 7 8

3 1 2
4 5  
6 7 8

3 1 2
4   5
6 7 8

3 1 2
  4 5
6 7 8

  1 2
3 4 5
6 7 8

Num of steps: 22
Num of expanded: 1104
Num of generated: 1742
Time consumed: 123

Total time consumed: 126

八數碼問題的另一個實現:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
#include<string>
#define inf 1<<30
#define eps 1e-7
#define LD long double
#define LL long long
#define maxn 1000000005
using namespace std;
struct Node{
	int maze[3][3];   //八數碼具體情況 
	int h,g;    //兩個估價函式
	int x,y;   //空位的位置
	int Hash;   //HASH值
	bool operator<(const Node n1)const{     //優先佇列第一關鍵字為h,第二關鍵字為g
		return h!=n1.h?h>n1.h:g>n1.g;
	}
	bool check(){    //判斷是否合法
		if(x>=0&&x<3&&y>=0&&y<3)
			return true;
		return false;
	}
}s,u,v,tt;
int HASH[9]={1,1,2,6,24,120,720,5040,40320};   //HASH的權值
int destination=322560;   //目標情況的HASH值 
/*
目標狀態:
1 2 3
4 5 6 
7 8 0
其hash值為322560
*/
int vis[400000];            //判斷狀態已遍歷,初始為-1,否則為到達這步的轉向
int pre[400000];        //路徑儲存
int way[4][2]={{0,1},{0,-1},{1,0},{-1,0}};   //四個方向
void debug(Node tmp){
	for(int i=0;i<3;i++){
		for(int j=0;j<3;j++)
			printf("%d ",tmp.maze[i][j]);
		printf("\n");
	}
	printf("%d %d\n%d %d\n",tmp.x,tmp.y,tmp.g,tmp.h);
	printf("hash=%d\n",tmp.Hash);
}
int get_hash(Node tmp){    //獲得HASH值
	int a[9],k=0;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			a[k++]=tmp.maze[i][j];
	int res=0;
	for(int i=0;i<9;i++){
		int k=0;
		for(int j=0;j<i;j++)
			if(a[j]>a[i])
				k++;
		res+=HASH[i]*k;
	}
	return res;
}

bool isok(Node tmp){    //求出逆序對,判斷是否有解
	int a[9],k=0;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			a[k++]=tmp.maze[i][j];
	int sum=0;
	for(int i=0;i<9;i++)
		for(int j=i+1;j<9;j++)
			if(a[j]&&a[i]&&a[i]>a[j])
				sum++;
	return !(sum&1);    //由於目標解為偶數,所以狀態的逆序數為偶數才可行,交換空格,逆序數增幅為偶數,故初始節點和目標的節點的逆序數奇偶性相同
}

int get_h(Node tmp){   //獲得估價函式H
	int ans=0;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			if(tmp.maze[i][j])
				ans+=abs(i-(tmp.maze[i][j]-1)/3)+abs(j-(tmp.maze[i][j]-1)%3);
	return ans;
}
void astar(){    //搜尋
	priority_queue<Node>que;
	que.push(s);
	while(!que.empty()){
		u=que.top();
		que.pop();
		for(int i=0;i<4;i++){
			v=u;
			v.x+=way[i][0];
			v.y+=way[i][1];
			if(v.check()){
				swap(v.maze[v.x][v.y],v.maze[u.x][u.y]);   //將空位和相鄰位交換
				v.Hash=get_hash(v);			    //得到HASH值
				if(vis[v.Hash]==-1&&isok(v)){   //判斷是否已遍歷且是否可行,後者可以不要
					vis[v.Hash]=i;           //儲存方向
					v.g++;;                  //已花代價+1
					pre[v.Hash]=u.Hash;     //儲存路徑
					v.h=get_h(v);           //得到新的估價函式H
					que.push(v);     //入隊
				}
				if(v.Hash==destination)
					return ;
			}
		}
	}
}
void print(){
	string ans;
	ans.clear();
	int nxt=destination;
	while(pre[nxt]!=-1){  //從終點往起點找路徑
		switch(vis[nxt]){   //四個方向對應
		case 0:ans+='r';break;
		case 1:ans+='l';break;
		case 2:ans+='d';break;
		case 3:ans+='u';break;
		}
		nxt=pre[nxt];	
	}
	for(int i=ans.size()-1;i>=0;i--)
		putchar(ans[i]);
	puts("");
}
int main(){
	Node test;
	/*int value = 0;
	for( int i = 0; i < 3; i++ )
	{
		for( int j = 0; j < 3; j++ )
		{
			test.maze[i][j] = value++;
		}
	}*/

	//cout << get_hash(test) << endl;
	
	char str[100];
	while(gets(str)!=NULL){
		int k=0;
		memset(vis,-1,sizeof(vis));
		memset(pre,-1,sizeof(pre));
		for(int i=0;i<3;i++)
			for(int j=0;j<3;j++){
				if((str[k]<='9'&&str[k]>='0')||str[k]=='x'){
					if(str[k]=='x'){
						s.maze[i][j]=0;
						s.x=i;
						s.y=j;
					}
					else
						s.maze[i][j]=str[k]-'0';
				}
				else
					j--;
				k++;
			}
			if(!isok(s)){   //起始狀態不可行
				printf("unsolvable\n");
				continue;
			}
			s.Hash=get_hash(s);
			if(s.Hash==destination){   //起始狀態為目標狀態
				puts("");
				continue;
			}
			vis[s.Hash]=-2;
			s.g=0;s.h=get_h(s);
			astar();
			print();
	}
	return 0;
}

/*
輸入格式:
1234567x8表示:x代表空格,0表示空格
1 2 3
4 5 6
7 0 8
終態:
1 2 3
4 5 6
7 8 0
*/

轉載來源:http://blog.csdn.net/acm_cxlove/article/details/7745323

人工智慧A*課件: