八數碼問題
一.八數碼問題
八數碼問題也稱為九宮問題。在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 9
class 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=;//方向
int teight::total=0;//步數
teight::teight(char *fname)
bool teight::operator==(const teight &t)//判斷兩個狀態是否相同
資料檔案的結構:
一共三行,第一行是用空格隔開的九個數字0~8,這是初始狀態。第二行是乙個數字,空格(數字0)的位置,第三行也是用空格隔開的九個數字0~8,這是目標狀態。
三.線性表
搜尋法在搜尋過程中,需要使用乙個佇列儲存搜尋的中間結點,為了在找到目標結點後,能夠找到從初始結點到目標結點的路徑,需要保留所有搜尋過的結點。另一方面,不同問題甚至同一問題的不同搜尋方法中,需要儲存的結點數量相差很大,所以這裡採用鏈式線性表作為儲存結構,同時,為適應不同問題,線性表設計成類模板形式。
templateclass tlist; //線性表前視定義
templateclass tnode //線性表結點類模板
tnode(const type& dat);
private:
tnode* next;
type data;
};templateint tlist::insert(const type& t,int k)
if(k>length-1)
if(k>0 && k*q=first;
while(k-->0)
q=q->next;
p->next=q->next;
q->next=p;}}
else
length++;
return 1;
}templatetype tlist::getdata(int k)
templatevoid tlist::setdata(const type& t,int k)
線性表單獨以標頭檔案形式存放。
void tbfs::printl(tlist&l)
}int tbfs::repeat(tlist&l)
//建構函式
tastar(char *fname); //帶引數建構函式
virtual void search(); //a*搜尋法
private:
int f,g,h; //估價函式
int r[num]; //儲存狀態中各個數字位置的輔助陣列
static int s[num]; //儲存目標狀態中各個數字位置的輔助陣列
static int e; //儲存各個數字相對距離的輔助陣列
void printl(tlistl); //成員函式,輸出搜尋路徑
int expend(int i); //成員函式,a*演算法的狀態擴充套件函式
int calcuf(); //成員函式,計算估價函式
void sort(tlist& l,int k); //成員函式,將新擴充套件結點按f從小到大順序插入待擴充套件結點佇列
int repeat(tlist&l); //成員函式,檢查結點是否重複
};int tastar::s[num],tastar::e[num*num];
tastar::tastar(char *fname):teight(fname)
void tastar::printl(tlistl)
}int tastar::expend(int i)
return 0;
}int tastar::calcuf()
l.insert(*this,i);
}int tastar::repeat(tlist&l)
eight.txt檔案中的資料(初始態和目標態):
一共三行,第一行是用空格隔開的九個數字0~8,這是初始狀態。第二行是乙個數字,空格(數字0)的位置,第三行也是用空格隔開的九個數字0~8,這是目標狀態。
8 3 5 1 2 7 4 6 0
81 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
eight_result.txt中的結果(執行後得到的結果)
七、演算法執行結果
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是奇數,所以他奇排列。
因此,可以在執行程式前檢查初始狀態和目標狀態的窘是否相同,相同則問題可解,應當能搜尋到路徑。否則無解。
八數碼問題
2 6 4 1 3 7 0 5 8 8 1 5 7 3 6 4 0 2 樣例輸出 還有就是重判的問題,如何重判呢?第一種方法 把排列變成整數,然後只開乙個一維陣列,也就是說設計一套排列的編碼和解碼函式,把0 8的全排列和0 362879的整數意義一一對應起來。時間效率高,但編碼解碼法適用範圍並不大,...
八數碼問題
八數碼問題 題意 編號為1 8的8個正方形滑塊被擺成3行3列 有乙個格仔留空 如下圖所示 每次可以把與空格相鄰的滑塊 有公共邊才算相鄰 移到空格中,而他原來的位置 就成為了新的空格。如果無法到達目標局面,則輸出 1。2 6 4 13 75 8 移到後 8 1 5 73 642 樣例輸入 2 6 4 ...
八數碼問題
搜尋演算法學問不小.總結 1.狀態表示用整數最快,可是轉化狀態的 不好寫,用字串挺爽的,可有些地方涉及到數字運算,又不自然,整來整去,還是用byte好了.效能沒比字串強多少.2.open錶用linkedlist就挺好,支援佇列和堆疊兩種模型,這點在雙向廣度優先搜尋時候挺方便,closed表千萬別用l...