今天偶爾看到了一個算法問題(八皇后問題),回想一下還是在算法課上學習過的,於是,自己總結了一下,寫了這篇日志
算法提出:
在國際象棋棋盤上(8*8)放置八個皇后,使得任意兩個皇后之間不能在同一行,同一列,也不能位於同於對角線上。問共有多少種不同的方法,並且指出各種不同的放法。
算法思路:
首先我們分析一下問題的解,我們每取出一個皇后,放入一行,共有八種不同的放法,然后再放第二個皇后,同樣如果不考慮規則,還是有八種放法。於是我們可以用一個八叉樹來描述這個過程。從根節點開始,樹每增加一層,便是多放一個皇后,直到第8層(根節點為0層),最后得到一個完全八叉樹。
緊接着我們開始用深度優先遍歷這個八叉樹,在遍歷的過程中,進行相應的條件的判斷。以便去掉不合規則的子樹。
那么具體用什么條件來進行子樹的裁剪呢?
我們先對問題解的結構做一個約定。
用X[i]來表示,在第i行,皇后放在了X[i]這個位置。
於是我們考慮第一個條件,不能再同一行,同一列於是我們得到x[i]不能相同。剩下一個條件是不能位於對角線上,這個條件不是很明顯,我們經過分析得到,設兩個不同的皇后分別在j,k行上,x[j],x[k]分別表示在j,k行的那一列上。那么不在同一對角線的條件可以寫為abs((j-k))!=abs(x[j]-x[k]),其中abs為求絕對值的函數。
於是下面我們便可以利用一個遞歸的調用來遍歷八叉樹。
我們首先定義一個訪問某節點所有子節點的函數
void backtrack(int t) { if(t>num) //num為皇后的數目 { sum++;//sum為所有的可行的解 for(int m = 1;m<num;m++) { cout<<x[m];//這一行用輸出當遞歸到葉節點的時候,一個可行解 } cout<<endl; } else for(int i = 1;i<=num;i++) { x[t] = i; if(place(t)) backtrack(t+1);//此處的place函數用來進行我們上面所說的條件的判斷,如果成立,進入下一級遞歸 } }
下面我們定義了條件判斷函數
bool place(int k) { for(int j = 1;j<k;j++) if(abs(x[k] - x[j]) == abs(k-j)||x[j] == x[k]) return false; return true; }
上面的函數便是按照我們上面說介紹的條件進行判斷。
最后就是主程序的調用了
static int num; static int *x; static int sum; void main() { num = 8; sum = 0; x = new int[num+1]; for(int i= 0;i<=num;i++) x[i] = 0; backtrack(1); cout<<"方案共有"<<sum; }
通過上面的總結,是自己對整個算法有了更深刻的理解,熟悉的回溯法的思想。