八數位問題
一.八數位問題
八數位問題也稱為九宮問題。在3×3的棋盤,擺有八個棋子,每個棋子上標有1至8的某一數字,不同棋子上標的數字不相同。棋盤上還有一個空格,與空格相鄰的棋子可以移到空格中。要求解決的問題是:給出一個初始狀態和一個目標狀態,找出一種從初始轉變成目標狀態的移動棋子步數最少的移動步驟。
所謂問題的一個狀態就是棋子在棋盤上的一種擺法。棋子移動後,狀態就會發生改變。解八數位問題實際上就是找出從初始狀態到達目標狀態所經過的一系列中間過渡狀態。
八數位問題一般使用搜尋法來解。
搜尋法有廣度優先搜尋法、深度優先搜尋法、A*演算法等。這裡通過用不同方法解八數位問題來比較一下不同搜尋法的效果。
二.搜尋演算法基類
1.八數位問題的狀態表示
八數位問題的一個狀態就是八個數字在棋盤上的一种放法。每個棋子用它上面所標的數字表示,並用0表示空格,這樣就可以將棋盤上棋子的一個狀態儲存在一個一維數組p[9]中,儲存的順序是從左上方開始,自左至右,從上到下。也可以用一個二維數組來存放。
2.結點
搜尋演算法中,問題的狀態用結點描述。結點中除了描述狀態的數組p[9]外,還有一個父結點指標last,它記錄了當前結點的父結點編號,如果一個結點v是從結點u經狀態變化而產生的,則結點u就是結點v的父結點,結點v的last記錄的就是結點u的編號。在到達目標結點後,通過last 可以找出搜尋的路徑。
3.類的結構
在C++中用類來表示結點,類將結點有關的資料操作封裝在一起。
不同的搜尋演算法具有一定共性,也有各自的個性,因此這裡將不同搜尋演算法的共有的資料和功能封裝在一個基類中,再通過繼承方式實現不同的搜尋演算法。
4.結點擴充規則
搜尋就是按照一定規則擴充已知結點,直到找到目標結點或所有結點都不能擴充為止。
八數位問題的結點擴充應當遵守棋子的移動規則。按照棋子移動的規則,每一次可以將一個與空格相鄰棋子移動到空格中,實際上可以看作是空格作相反移動。空格移動的方向可以是右、下、左、上,當然不能移出邊界。
棋子的位置,也就是儲存狀態的數組元素的下標。空格移動後,它的位置發生變化,在不移出界時,空格向右、下、左和上移動後,新位置是原位置分別加上1、3、-1、-3,如果將空格向右、下、左和上移動分別用0、1、2、3表示,並將-3、3、-1、1放在靜態數組d[4]中,空格位置用spac表示,那麼空格向方向i移動後,它的位置變為spac+d[i]。
空格移動所產生的狀態變化,反映出來則是將數組p[]中,0的新位置處的數與0交換位置。
5.八數位問題的基類
八數位問題的基類及其成員函數的實現如下:
#define Num 9class TEight{public:TEight(){}TEight(char *fname);virtual void Search()=0;protected:int p[Num];int last,spac;static int q[Num],d[],total;void Printf();bool operator==(const TEight &T);bool Extend(int i);};int TEight::q[Num];int TEight::d[]={1,3,-1,-3};int TEight::total=0;TEight::TEight(char *fname){ifstream fin;fin.open(fname,ios::in | ios::nocreate);if(!fin){cout<<"不能開啟資料檔案!"<<endl;return;}int i;for(i=0;i<Num;)fin>>p[i++];fin>>spac;for(i=0;i<Num;)fin>>q[i++];fin.close();last=-1;total=0;}void TEight::Printf(){ofstream fout;fout.open("Eight_result.txt",ios::ate|ios::app);fout<<total++<<"t";for(int i=0;i<Num;)fout<<" "<<p[i++];fout<<endl;fout.close();}bool TEight::operator==(const TEight &T){for(int i=0;i<Num;)if(T.p[i]!=p[i++])return 0;return 1;}bool TEight::Extend(int i){if(i==0 && spac%3==2 || i==1 && spac>5 || i==2 && spac%3==0 || i==3 && spac<3)return 0;int temp=spac;spac+=d[i];p[temp]=p[spac];p[spac]=0;return 1;}資料檔案的結構:
一共三行,第一行是用空格隔開的九個數字0~8,這是初始狀態。第二行是一個數字,空格(數字0)的位置,第三行也是用空格隔開的九個數字0~8,這是目標狀態。
三.線性表
搜尋法在搜尋過程中,需要使用一個佇列儲存體搜尋的中間結點,為了在找到目標結點後,能夠找到從初始結點到目標結點的路徑,需要保留所有搜尋過的結點。另一方面,不同問題甚至同一問題的不同搜尋方法中,需要儲存的結點數量相差很大,所以這裡採用鏈式線性表作為儲存結構,同時,為適應不同問題,線性表設計成類模板形式。
template<class Type> class TList; //線性表前視定義template<class Type> class TNode //線性表結點類模板{ friend class TList<Type>; public: TNode(){} TNode(const Type& dat);private: TNode<Type>* Next; Type Data;};template<class Type> class TList{public: TList(){Last=First=0;Length=0;} //建構函式 int Getlen()const{return Length;} //成員函數,返回線性表長度 int Append(const Type& T); //成員函數,從表尾加入結點 int Insert(const Type& T,int k); //成員函數,插入結點 Type GetData(int i); //成員函數,返回結點資料成員 void SetData(const Type& T,int k); //成員函數,設定結點資料成員 private: TNode<Type> *First,*Last; //資料成員,線性表首、尾指標 int Length; //資料成員,線性表長度};template<class Type> int TList<Type>::Append(const Type& T){ Insert(T,Length); return 1;}template<class Type> int TList<Type>::Insert(const Type& T,int k){ TNode<Type> *p=new TNode<Type>; p->Data=T; if(First) { if(k<=0) { p->Next=First; First=p; } if(k>Length-1) { Last->Next=p; Last=Last->Next; Last->Next=0; } if(k>0 && k<Length) { k--; TNode<Type> *q=First; while(k-->0) q=q->Next; p->Next=q->Next; q->Next=p; } } else { First=Last=p; First->Next=Last->Next=0; } Length++; return 1;}template<class Type> Type TList<Type>::GetData(int k){ TNode<Type> *p=First; while(k-->0) p=p->Next; return p->Data;}template<class Type> void TList<Type>::SetData(const Type& T,int k){ TNode<Type> *p=First; while(k-->0) p=p->Next; p->Data=T;}線性表單獨以標頭檔形式存放。
四.廣度優先搜尋法
在搜尋法中,廣度優先搜尋法是尋找最短路經的首選。
1.廣度優先搜尋演算法的基本步驟
1)建立一個隊列,將初始結點入隊,並設定隊列頭和尾指標
2)取出隊列頭(頭指標所指)的結點進行擴充,從它擴充出子結點,並將這些結點按擴充的順序排入佇列。
3)如果擴充出的新結點與隊列中的結點重複,則拋棄新結點,跳至第六步。
4)如果擴充出的新結點與隊列中的結點不重複,則記錄其父結點,並將它排入佇列,更新隊列尾指標。
5)如果擴充出的結點是目標結點,則輸出路徑,程式結束。否則繼續下一步。
6)如果隊列頭的結點還可以擴充,直接返回第二步。否則將隊列頭指標指向下一結點,再返回第二步。
2.搜尋路徑的輸出
搜尋到目標結點後,需要輸出搜尋的路徑。每個結點有一個資料域last,它記錄了結點的父結點,因此輸出搜尋路徑時,就是從目標結點Q出發,根據last找到它的父結點,再根據這個結點的last找到它的父結點,....,最後找到初始結點。搜尋的路徑就是從初始結點循相反方向到達目標結點的路徑。
3.廣度優先搜尋法TBFS類的結構
廣度優先搜尋法TBFS類是作為TEight類的一個子類。其類的結構和成員函數的實現如下:
class TBFS:public TEight{public:TBFS(){}TBFS(char *fname):TEight(fname){}virtual void Search();private:void Printl(TList<TBFS> &L);int Repeat(TList<TBFS> &L);int Find();};void TBFS::Printl(TList<TBFS> &L){TBFS T=*this;if(T.last==-1)return;else{T=L.GetData(T.last);T.Printl(L);T.Printf();}}int TBFS::Repeat(TList<TBFS> &L){int n=L.Getlen();int i;for(i=0;i<n;i++)if(L.GetData(i)==*this)break;return i;}int TBFS::Find(){for(int i=0;i<Num;)if(p[i]!=q[i++])return 0;return 1;}void TBFS::Search(){TBFS T=*this;TList<TBFS> L;L.Append(T);int head=0,tail=0;while(head<=tail){for(int i=0;i<4;i++){T=L.GetData(head);if(T.Extend(i) && T.Repeat(L)>tail){T.last=head;L.Append(T);tail++;}if(T.Find()){T.Printl(L);T.Printf();return;}}head++;}}4.廣度優先搜尋法的缺點
廣度優先搜尋法在有解的情形總能保證搜尋到最短路經,也就是移動最少步數的路徑。但廣度優先搜尋法的最大問題在於搜尋的結點數量太多,因為在廣度優先搜尋法中,每一個可能擴充出的結點都是搜尋的對象。隨著結點在搜尋樹上的深度增大,搜尋的結點數會很快增長,並以指數形式擴張,從而所需的儲存空間和搜尋花費的時間也會成倍增長。
五.雙向廣度優先搜尋法
1.雙向廣度優先搜尋法
八數位問題具有可逆性,也就是說,如果可以從一個狀態A擴充出狀態B,那麼同樣可以從狀態B擴充出狀態A,這種問題既可以從初始狀態出發,搜尋目標狀態,也可以從目標狀態出發,搜尋初始狀態。對這類問題如果採用雙向廣度優先搜尋法,將可以大大節省搜尋的時間。
所謂雙向廣度優先搜尋法,是同時從初始狀態和目標狀態出發,採用廣度優先搜尋的策略,向對方搜尋,如果問題存在解,則兩個方向的搜尋會在中途相遇,即搜尋到同一個結點。將兩個方向的搜尋路徑串連起來,就可以得到從初始結點到目標結點的搜尋路徑。
2.雙向廣度優先搜尋演算法
雙向廣度優先搜尋演算法的基本步驟如下:
1)建立兩個隊列,一個是正向搜尋的隊列,另一個是反向搜尋的隊列。將初始結點放入正向隊列,將目標結點放入反向隊列,並設定兩個隊列的頭和尾指標。
2)從正向隊列取出隊列頭(頭指標所指)的結點進行擴充。
3)如果擴充出的新結點與隊列中的結點重複,則拋棄新結點,跳至第六步。
4)如果擴充出的新結點與隊列中的結點不重複,則記錄其父結點,並將它排入佇列,更新隊列尾指標。
5)檢查擴充出的結點是否在另一方向的隊列中,如果是則兩個方向的搜尋相遇,顯示搜尋路徑,程式結束。否則繼續下一步。
6)如果隊列頭的結點還可以擴充,直接返回第二步。否則將隊列頭指標指向下一結點,然後對另一方向搜尋的隊列,按照第二步開始的同樣步驟處理。
3.雙向廣度優先搜尋法的優勢
廣度優先搜尋法搜尋時,結點不斷擴張,深度越大,結點數越多。如果從兩個方向向對方搜尋,就會在路徑中間某個地方相會,這樣,雙方的搜尋的深度都不大,所搜尋過的結點數就少得多,搜尋時間也就節省不少。
從理論上說,如果每一結點可擴充的子結點數為m,廣度優先搜尋的搜尋樹就是一顆m叉樹,也就是每個結點都由m個分支。按完全m叉樹計算,如果目標結點在第n層,廣度優先搜尋就必須在搜尋樹上擴充完n-1層的所有結點,擴充的結點數為m(mn-1)/(m-1)。對於雙向廣度優先搜尋來說,如果兩個方向的搜尋在第i層產生同一子結點,那麼正向搜尋擴充的結點數為m(mi-1)/(m-1),反向搜尋擴充的結點數為m(mn-i-1)/(m-1),搜尋的結點總數為m(mi+mn-i-1)/(m-1)(其中n是最優解路徑長度,i=(m+1) div 2,)。設n為偶數(n=2*i),廣度優先雙向搜尋擴充的結點數約是廣度優先搜尋的2/(mi/2+1)*100%,相對減少(mi/2-1)/(mi/2+1)*100%。
4.判斷兩個方向的搜尋相遇
在雙向廣度優先搜尋法中,如何判斷兩個方向的搜尋相遇呢。只要我們在產生結點的同時,判斷該結點是否出現在相反方向的搜尋樹上即可,也就是說,在某個方向搜尋中擴充出一個新結點,如果它與另一個方向已擴充出的結點重複,也就找到瞭解。
5.雙向廣度優先搜尋法的TDBFS類結構
雙向廣度優先搜尋法的TDBFS和廣度優先搜尋法類似,也是TEight類的子類,類結構及其成員函數的實現如下:
class TDBFS:public TEight{public:TDBFS(){}TDBFS(char *fname):TEight(fname){}virtual void Search();private:void Printp(TList<TDBFS> &L);void Printb(TList<TDBFS> &L);int Repeat(TList<TDBFS> &L); };void TDBFS::Printp(TList<TDBFS> &L){TDBFS T=*this;if(T.last==-1)return;else{T=L.GetData(T.last);T.Printp(L);T.Printf();}}void TDBFS::Printb(TList<TDBFS> &L){TDBFS T=*this;while(T.last>-1){T=L.GetData(T.last);T.Printf();} }int TDBFS::Repeat(TList<TDBFS> &L){int n=L.Getlen();int i;for(i=0;i<n;)if(L.GetData(i++)==*this)break;return i;}void TDBFS::Search(){TDBFS T1=*this;TDBFS T2;for(int i=0;i<Num;i++){T2.p[i]=q[i];if(q[i]==0)T2.spac=i;}T2.last=-1;TList<TDBFS> L1,L2;L1.Append(T1);L2.Append(T2);int head1=0,tail1=0,head2=0,tail2=0;while(head1<=tail1 || head2<=tail2){for(int i=0;i<4;i++){T1=L1.GetData(head1);if(T1.Extend(i) && T1.Repeat(L1)>tail1){T1.last=head1;L1.Append(T1);tail1++;}int m=T1.Repeat(L2);if(m<tail2){T1.Printp(L1);T1.Printf();T2=L2.GetData(m);T2.Printb(L2);return;}}head1++;for(i=0;i<4;i++){T2=L2.GetData(head2);if(T2.Extend(i) && T2.Repeat(L2)>tail2){T2.last=head2;L2.Append(T2);tail2++;}int m=T2.Repeat(L1);if(m<tail1){T1=L1.GetData(m);T1.Printb(L1);T1.Printf();T2.Printp(L2);return;}}head2++;}}
六.A*演算法
1.啟發學習法搜尋
廣度優先搜尋和雙向廣度優先搜尋都屬於盲目搜尋,這在狀態空間不大的情況下是很合適的演算法,可是當狀態空間十分龐大時,它們的效率實在太低,往往都是在搜尋了大量無關的狀態結點後才碰到解答,甚至更本不能碰到解答。
搜尋是一種試探性的查尋過程,為了減少搜尋的盲目性引,增加試探的準確性,就要採用啟發學習法搜尋了。所謂啟發學習法搜尋就是在搜尋中要對每一個搜尋的位置進行評估,從中選擇最好、可能容易到達目標的位置,再從這個位置向前進行搜尋,這樣就可以在搜尋中省略大量無關的結點,提高了效率。
2.A*演算法
A*演算法是一種常用的啟發學習法搜尋演算法。
在A*演算法中,一個結點位置的好壞用估價函數來對它進行評估。A*演算法的估價函數可表示為:
f'(n) = g'(n) + h'(n)
這裡,f'(n)是估價函數,g'(n)是起點到終點的最短路徑值(也稱為最小耗費或最小代價),h'(n)是n到目標的最短路經的啟發值。由於這個f'(n)其實是無法預Crowdsourced Security Testing道的,所以實際上使用的是下面的估價函數:
f(n) = g(n) + h(n)
其中g(n)是從初始結點到節點n的實際代價,h(n)是從結點n到目標結點的最佳路徑的估計代價。在這裡主要是h(n)體現了搜尋的啟發資訊,因為g(n)是已知的。用f(n)作為f'(n)的近似,也就是用g(n)代替g'(n),h(n)代替h'(n)。這樣必須滿足兩個條件:(1)g(n)>=g'(n)(大多數情況下都是滿足的,可以不用考慮),且f必須保持單調遞增。(2)h必須小於等於實際的從當前節點到達目標節點的最小耗費h(n)<=h'(n)。第二點特別的重要。可以證明應用這樣的估價函數是可以找到最短路徑的。
3.A*演算法的步驟
A*演算法基本上與廣度優先演算法相同,但是在擴充出一個結點後,要計算它的估價函數,並根據估價函數對待擴充的結點排序,從而保證每次擴充的結點都是估價函數最小的結點。
A*演算法的步驟如下:
1)建立一個隊列,計算初始結點的估價函數f,並將初始結點入隊,設定隊列頭和尾指標。
2)取出隊列頭(隊列頭指標所指)的結點,如果該結點是目標結點,則輸出路徑,程式結束。否則對結點進行擴充。
3)檢查擴充出的新結點是否與隊列中的結點重複,若與不能再擴充的結點重複(位於隊列頭指標之前),則將它拋棄;若新結點與待擴充的結點重複(位於隊列頭指標之後),則比較兩個結點的估價函數中g的大小,保留較小g值的結點。跳至第五步。
4)如果擴充出的新結點與隊列中的結點不重複,則按照它的估價函數f大小將它插入隊列中的頭結點後待擴充結點的適當位置,使它們按從小到大的順序排列,最後更新隊列尾指標。
5)如果隊列頭的結點還可以擴充,直接返回第二步。否則將隊列頭指標指向下一結點,再返回第二步。
4.八數位問題的A*演算法的估價函數
估價函數中,主要是計算h,對於不同的問題,h有不同的含義。那麼在八數位問題中,h的含意是各什麼。八數位問題的一個狀態實際上是數字0~8的一個排列,用一個數組p[9]來儲存它,數組中每個元素的下標,就是該數在排列中的位置。例如,在一個狀態中,p[3]=7,則數字7的位置是3。如果目標狀態數字3的位置是8,那麼數字7對目標狀態的位移距離就是3,因為它要移動3步才可以回到目標狀態的位置。
八數位問題中,每個數字可以有9個不同的位置,因此,在任意狀態中的每個數字和目標狀態中同一數位相對距離就有9*9種,可以先將這些相對距離算出來,用一個矩陣儲存,這樣只要知道兩個狀態中同一個數位位置,就可查出它們的相對距離,也就是該數位位移距離:
0 1 2 3 4 5 6 7 8
0 0 1 2 1 2 3 2 3 4
1 1 0 1 2 1 2 3 2 3
2 2 1 0 3 2 1 4 3 2
3 1 2 3 0 1 2 1 2 3
4 2 1 2 1 0 1 2 1 2
5 3 2 1 2 1 0 3 2 1
6 2 3 4 1 2 3 0 1 2
7 3 2 3 2 1 2 1 0 1
8 4 3 2 3 2 1 2 1 0
例如在一個狀態中,數字8的位置是3,在另一狀態中位置是7,那麼從矩陣的3行7列可找到2,它就是8在兩個狀態中的位移距離。
估價函數中的h就是全體數字位移距離之和。
顯然,要計算兩個不同狀態中同一數位位移距離,需要知道該數字在每個狀態中的位置,這就要對數組p[9]進行掃描。由於狀態發生變化,個數位位置也要變化,所以每次計算h都沿線掃描數組,以確定每個數字在數組中的位置。為了簡化計算,這裡用一個數組儲存狀態中各個數位位置,並讓它在狀態改變時隨著變化,這樣就不必在每次計算h時,再去掃描狀態數組。
例如,某個狀態中,數字5的位置是8,如果用數組r[9]儲存位置,那麼就有r[5]=8。
現在用數組r[9]儲存目前狀態的數字位置,而用s[9]儲存目標狀態的數字位置,那麼目前狀態數字i對目標狀態的位移距離就是矩陣中r[i]行s[i]列對應的值。
5.A*演算法的類結構
A*演算法的類聲明如下:
class TAstar:public TEight{public:TAstar(){} //建構函式TAstar(char *fname1,char *fname2); //帶參數建構函式 virtual void Search(); //A*搜尋法private:int f,g,h; //估價函數 int r[Num]; //儲存狀態中各個數字位置的輔助數組static int s[Num]; //儲存目標狀態中各個數字位置的輔助數組static int e[]; //儲存各個數字相對距離的輔助數組 void Printl(TList<TAstar> L); //成員函數,輸出搜尋路徑 int Expend(int i); //成員函數,A*演算法的狀態擴充函數 int Calcuf(); //成員函數,計算估價函數void Sort(TList<TAstar>& L,int k); //成員函數,將新擴充結點按f從小到大順序插入待擴充結點隊列int Repeat(TList<TAstar> &L); //成員函數,檢查結點是否重複};int TAstar::s[Num],TAstar::e[Num*Num];TAstar::TAstar(char *fname1,char *fname2):TEight(fname1){ for(int i=0;i<Num;){r[p[i]]=i; //儲存初始狀態個個數位位置s[q[i]]=i++; //儲存目標狀態個個數位位置}ifstream fin; fin.open(fname2,ios::in | ios::nocreate);//開啟資料檔案if(!fin){cout<<"不能開啟資料檔案!"<<endl;return;}for(i=0;i<Num*Num;i++) //讀入各個數字相對距離值 fin>>e[i];fin.close();f=g=h=0; //估價函數初始值 }void TAstar::Printl(TList<TAstar> L) {TAstar T=*this;if(T.last==-1)return;else{T=L.GetData(T.last);T.Printl(L);T.Printf();}}int TAstar::Expend(int i) {if(Extend(i)) //結點可擴充{int temp=r[p[r[0]]]; //改變狀態後數字位置變化,儲存改變後的位置 r[p[r[0]]]=r[0];r[0]=temp;return 1;}return 0;}int TAstar::Calcuf(){h=0;for(int i=0;i<Num;i++) //計算估價函數的hh+=e[Num*r[i]+s[i]];return ++g+h; }void TAstar::Sort(TList<TAstar>& L,int k){int n=L.Getlen();for(int i=k+1;i<n;i++){TAstar T=L.GetData(i);if(this->f<=T.f)break;}L.Insert(*this,i);}int TAstar::Repeat(TList<TAstar> &L){int n=L.Getlen();for(int i=0;i<n;i++)if(L.GetData(i)==*this)break;return i;}void TAstar::Search(){TAstar T=*this; //初始結點T.f=T.Calcuf(); //初始結點的估價函數TList<TAstar> L; //建立隊列L.Append(T); //初始結點入隊int head=0,tail=0; //隊列頭和尾指標while(head<=tail) //隊列不空則迴圈{for(int i=0;i<4;i++) //空格可能移動方向{T=L.GetData(head); //去隊列頭結點if(T.h==0) //是目標結點{ T.Printl(L);//輸出搜尋路徑T.Printf(); //輸出目標狀態return; //結束}if(T.Expend(i)) //若結點可擴充{int k=T.Repeat(L); //返回與已擴充結點重複的序號 if(k<head) //如果是不能擴充的結點continue; //丟棄T.last=head; //不是不能擴充的結點,記錄父結點T.f=T.Calcuf(); //計算fif(k<=tail) //新結點與可擴充結點重複{TAstar Temp=L.GetData(k); if(Temp.g>T.g) //比較兩結點g值L.SetData(T,k); //保留g值小的continue;} T.Sort(L,head) ; //新結點插入可擴充結點隊列 tail++; //隊列尾指標後移 }}head++; //一個結點不能再擴充,隊列頭指標指向下一結點}}
七.測試程式
A*演算法的測試:
int main(){ TAstar aStar("eight.txt","eight_dis.txt"); aStar.Search(); return 0;}eight.txt檔案中的資料(初始態和目標態):
一共三行,第一行是用空格隔開的九個數字0~8,這是初始狀態。第二行是一個數字,空格(數字0)的位置,第三行也是用空格隔開的九個數字0~8,這是目標狀態。
8 3 5 1 2 7 4 6 0
8
1 2 3 4 5 6 7 8 0
eight_dis.txt中的資料(估計函數使用):
0 1 2 1 2 3 2 3 4
1 0 1 2 1 2 3 2 3
2 1 0 3 2 1 4 3 2
1 2 3 0 1 2 1 2 3
2 1 2 1 0 1 2 1 2
3 2 1 2 1 0 3 2 1
2 3 4 1 2 3 0 1 2
3 2 3 2 1 2 1 0 1
4 3 2 3 2 1 2 1 0
八.演算法運行結果
1.BFS演算法只能適用於到達目標結點步數較少的情況,如果步數超過15步,已耗用時間太長,實際上不再起作用。
2.對於隨機產生的同一個可解狀態,BFS演算法最慢,DBFS演算法較慢,A*演算法較快。但在15步以內,DBFS演算法與A*演算法相差時間不大,超過15步後,隨步數增加,A*演算法的優勢就逐漸明顯,A*演算法要比DBFS演算法快5倍以上,並隨步數增大而增大。到25步以上,DBFS同樣因已耗用時間過長而失去價值。
3.一般來說,解答的移動步數每增加1,程式已耗用時間就要增加5倍以上。由於八數位問題本身的特點,需要檢查的節點隨步數增大呈指數形式增加,即使用A*演算法,也難解決移動步數更多的問題。
九.問題可解性
八數位問題的一個狀態實際上是0~9的一個排列,對於任意給定的初始狀態和目標,不一定有解,也就是說從初始狀態不一定能到達目標狀態。因為排列有奇排列和偶排列兩類,從奇排列不能轉化成偶排列或相反。
如果一個數字0~8的隨機排列871526340,用F(X)表示數字X前面比它小的數的個數,全部數位F(X)之和為Y=∑(F(X)),如果Y為奇數則稱原數位排列是奇排列,如果Y為偶數則稱原數位排列是偶排列。
例如871526340這個排列的
Y=0+0+0+1+1+3+2+3+0=10
10是偶數,所以他偶排列。871625340
Y=0+0+0+1+1+2+2+3+0=9
9是奇數,所以他奇排列。
因此,可以在運行程式前檢查初始狀態和目標狀態的窘是否相同,相同則問題可解,應當能搜尋到路徑。否則無解。