華為機試—N皇后問題(高階題160分:兩種回溯法解決 吐血整理)
一、問題描述:
在n×n格的棋盤上放置彼此不受攻擊的n個皇后。按照國際象棋的規則,皇后可以攻擊與之處在同一行或同一列或同一斜線上的棋子。n後問題等價於再n×n的棋盤上放置n個皇后,任何2個皇后不妨在同一行或同一列或同一斜線上。
輸入:
給定棋盤的大小n (n ≤ 13)
輸出:
輸出有多少种放置方法。
二、解題思路:
要解決N皇后問題,其實就是要解決好怎麼放置這n個皇后,每一個皇后與前面的所有皇后不能在同一行、同一列、同一對角線,在這裡我們可以以行優先,就是說皇后的行號按順序遞增,只考慮第i個皇后放置在第i行的哪一列,所以在放置第i個皇后的時候,可以從第1列判斷起,如果可以放置在第1個位置,則跳到下一行放置下一個皇后。如果不能,則跳到下一列...直到最後一列,如果最後一列也不能放置,則說明此時放置方法出錯,則回到上一個皇后向之前放置的下一列重新放置。此即是回溯法的精髓所在。當第n個皇后放置成功後,即得到一個可行解,此時再回到上一個皇后重新放置尋找下一個可行解...如此後,即可找出一個n皇后問題的所有可行解。
三、複雜度分析:
關於N皇后問題的複雜度問題可以說是眾說紛紜了,自己也改變過好幾次,剛開始以為棋盤是n行n列,所以理所當然應該是n^2,後來發現在每列選擇可否放置的比較上又做了一次迴圈,所以應該是n^3,但想了很久,發現判斷可否放置的時候不是每次都迴圈到n,它是根據皇后i的取值而變化的,所以複雜度應該是1/3 n^3左右,即是小於n^3的。
四、測試程式碼:
兩個實現方法,一個是遞歸回溯,一個是迭代回溯,思路都一樣,只是形式不同罷了。
遞歸回溯
#include <stdio.h> #include <math.h> #define N 15 int n; //皇后個數 int sum = 0; //可行解個數 int x[N]; //皇后放置的列數 /* *判斷函式,判斷第k個皇后是否可以放在某一個位置 *如果與之前的皇后出現在同一列或同一對角線則放置失敗,返回0,否則返回1 */ int place(int k) { int i; for(i=1;i<k;i++) if(abs(k-i)==abs(x[k]-x[i]) || x[k] == x[i]) return 0; return 1; } /* *求解可行解函式,當第t個皇后可以放置在t行的某一位置時,繼續放置下一皇后,直到 *所有皇后放置結束,如果某一皇后不能放置,則移向下一列放置,如果這一列都不能放 *置或所有皇后放置結束,返回上一皇后重新放置,最終返回所有可行解個數。 */ int queen(int t) { if(t>n) //當放置的皇后超過n時,可行解個數加1 sum++; else for(int i=1;i<=n;i++) { x[t] = i; //標明第t個皇后放在第i列 if(place(t)) //如果可以放在某一位置,則繼續放下一皇后 queen(t+1); } return sum; } int main() { int t; while(~scanf("%d",&n)){ t = queen(1); if(n == 0) //如果n=0,則可行解個數為0,這種情況一定不要忽略 t = 0; printf("%d\n",t); sum=0; } return 0; }
迭代回溯
#include <stdio.h> #include <math.h> #define N 15 int n; int sum = 0; int x[N]; int place(int k) { int i; for(i=1;i<k;i++) if(abs(k-i)==abs(x[k]-x[i]) || x[k] == x[i]) return 0; return 1; } int queen() { x[1] = 0; int t=1; while(t>0) { x[t]+=1; while(x[t]<=n && !place(t)) x[t]++; if(x[t]<=n) if(t == n) { for(int k=1;k<=n;k++) printf("%d ",x[k]); printf("\n"); sum++; } else x[++t] = 0; else t--; } return sum; } int main() { int t; while(scanf("%d",&n)){ t = queen(); printf("%d\n",t); sum=0; } return 0; }
五、測試結果:
六、總結
迭代回溯的註釋因為和遞歸回溯差不多,所以就不再附註了。在這裡我們可以看到,遞歸回溯非常簡單,結構很清晰,但它有一個潛在的問題存在,即當隨著變數n的增大,遞迴法的複雜度也將成幾何級增長,也有可能會出現重複的情況,所以我們在解決問題時,如果能用迭代法解決,最好還是不要用遞迴法,除非你已經對這個遞迴瞭如指掌了。
通過這個N皇后問題,我想大概已經把回溯法講得很清楚了吧,回溯法得到的解展開就是一個樹,很多方法都是可以通過回溯法來解決的,效率很高,但如果基數過大的話,回溯法就顯得不是那麼適用了,這也是回溯法的弱勢吧。比如說這個N皇后問題,好像當n>60的時候,回溯法就不能完全地解決問題了,這時可以考慮用概率演算法來解決,它可以解決很大的基數,只不過結果不是很精確而已。所以我們在面對一個問題時,具體是使用什麼演算法還是要結合實際情況來考慮的,目的都是更方便、更準確地解決問題。