用STL實現先深搜尋及先寬搜尋
——
N
皇后問題例子前兩天在sharpdew的BLOG (http://blog.csdn.net/sharpdew) 上看到關於N皇后問題的文章,sharpdew的演算法很漂亮,效率很高。我只是想從測試的角度出發試一下我的DFS/BFS演算法,所以試著寫了一下。不過我可以告訴大家,我的程式比sharpdew的至少慢了好幾倍,我想原因是STL容器(如vector、stack、queue等)在搜尋樹的大小變大時效率下降。畢竟sharpdew的演算法全都使用了long類型來處理,效率不可同日而語。N皇后問題說的是,在一個NxN的棋盤中放置N個皇后(國際象棋的),要求每個皇后之間不能相互攻擊(即同一行、同一列、同一條斜線上都不能有兩個皇后)。舉個例子,N=8時,以下狀態是一個解:
我不準備輸出所有解答,只是搜尋一下對於指定的N,共有多少解答。和sharpdew的程式一樣,我也把N限定在32以下,由於N=1、2、3時,顯然沒有解,所以我假定N在4-32之間。與我們在數獨程式中所做的一樣,我們需要的是一個可以配合DFS/BFS的狀態類,我把它命名為QueenState。除了nextStep()和isTarget()之外,它還需要什麼呢?首先是資料成員,即如何表示一個棋盤狀態。Sharpdew的程式中使用的是long中的bit,非常精巧,但是也非常難懂(所以,你可以看到sharpdew的程式裡有很多注釋,即使如此,你也得非常小心和認真地看才能弄懂)。我想用簡單一些的表示方法,所以我用了一個int數組,每個元素表示棋盤的一行中皇后所在的位置,行、列均從0起算。如a[0] = 0表示第一行的皇后在第一列,a[1] = 7則表示第二行的皇后在第八列。顯然棋盤的每行只能有一個皇后,所以這種表示方法是可行。當然我們還要表示皇后的位置未確定的狀態,我用-1來表示。所以,QueenState的資料成員有:表示棋盤大小的n_,表示皇后位置的數組lines_。本來這已足夠,但是為了便於我們尋找和處理,我還增加了一個int來表示當前棋盤中已確定皇后位置的行數,命名為cur_line_。現在,QueenState的定義如下:class QueenState{public: QueenState(int n); void nextStep(vector<QueenState>& vq) const; bool isTarget() const; private: int n_, cur_line_; int lines_[MAXQUEENNUMBER];}; 我們來從簡單的開始寫,首先是建構函式,它很簡單,對三個資料成員進行初始化就是了。n_是傳進來的,cur_line_初始化為0,數組中的所有元素初始化為-1,如下: QueenState(int n) : n_(n), cur_line_(0) { fill_n(lines_, n_, -1);} 接下來是isTarget(),它更簡單,只要檢查一下cur_line_看看是否所有行都已確定了皇后的位置。如下: bool isTarget() const { return cur_line_ == n_; } 最後是nextStep(),它稍微複雜一些。我們要根據前cur_line_行中的皇后位置來確定第cur_line_+1行中可以放置皇后的位置。方法是,從第一行開始,遍曆所有已確定了皇后位置的行,每迭代一行,就根據該行的皇后位置排除掉第cur_line_+1行中那些衝突的列(包括直線和斜線衝突)。遍曆結束後,剩下的未被排除的位置就是可以放置皇后的位置。我們根據這些位置逐個產生狀態空間樹的下一層結點,就可返回了。我們使用一個bool數組來記錄某個位置是否被排除。void QueenState::nextStep(vector<QueenState>& vq) const{ QueenState newState(n_); bool pos[MAXQUEENNUMBER]; fill_n(pos, n_, true); for (int i = 0; i < cur_line_; ++i) { pos[lines_[i]] = false; // 與已有皇后不能同列 int j = lines_[i] + (cur_line_ - i); // 不能同一斜線 if (j < n_) { pos[j] = false; } j = lines_[i] - (cur_line_ - i); if (j >= 0) { pos[j] = false; } } for (int i = 0; i < n_; ++i) { if (pos[i]) { newState = *this; newState.lines_[cur_line_] = i; // 下一行皇后的一個可能位置 ++newState.cur_line_; // 已確定行數加一 vq.push_back(newState); } }} QueenState就是這麼多了,接下來是那個DFS/BFS要求的結果處理函數。我在前面說了,我不打算對搜尋到的結果進行任何處理,只是簡單累計一下結果的數量,而這一點DFS/BFS已經代勞了。所以,我們只要簡單地返回一個false來要求DFS/BFS繼續搜尋就行了。bool continueSearch(const QueenState&){ return false;} 最後就是主程式了,我們要做的就是,從命令列讀入棋盤的大小,產生一個初始化棋盤狀態,然後調用DFS/BFS函數模板,取回調用結果並顯示,完了。int main(int argc, char *argv[]){ if (argc < 2) { cerr << "Usage: queen <Queen_number>" << endl; return 1; } int n = atoi(argv[1]); if (n < MINQUEENNUMBER || n > MAXQUEENNUMBER) { cerr << "Queen_number too large or too small" << endl; return 1; } cout << "Queen_number = " << n << endl; QueenState initState(n); int total = DepthFirstSearch(initState, &continueSearch); cout << "Total = " << total << endl; return 0;} 從前面的分析可以看出,皇后問題的搜尋是從第一行開始,一行一行向下的,狀態空間樹中的每一層結點都比上一層結點多了一行確定的皇后位置,所以不存在搜尋過程中狀態重複的情況。這一點與數獨問題的解法一樣,所以我們的DFS/BFS可以正常運行。下一次,我們將面對可能出現結點狀態重複的情況,這需要對目前這個簡單的DFS/BFS演算法進行一定的改進。