1. 程式人生 > >回溯法解決N皇后問題

回溯法解決N皇后問題

八皇后問題

在棋盤上放置8個皇后,使得它們互不攻擊,此時每個皇后的攻擊範圍為同行同列和同對角線,要求找出所有解。
遞迴函式將不再遞迴呼叫它自身,而是返回上一層呼叫,這種現象稱為回溯(backtracking)。
當把問題分成若干步驟並遞迴求解時,如果當前步驟沒有合法選擇,則函式將返回上一級遞迴呼叫,這種現象稱為回溯。 正是因為這個原因,遞迴列舉演算法常被稱為回溯法,應用十分普遍。

生成-測試法(低效)

// n皇后問題:生成-測試法
// Rujia Liu
#include<cstdio>
using namespace std;

// C索引表示行,值表示列
// tot表示解的個數
// nc表示執行遞迴函式次數 int C[50], tot = 0, n = 8, nc = 0; void search(int cur) { int i, j; nc++; if(cur == n) { for(i = 0; i < n; i++) for(j = i+1; j < n; j++) if(C[i] == C[j] || i-C[i] == j-C[j] || i+C[i] == j+C[j]) return; tot++; // 輸出正確結果 for(int m=0; m<n; m++) { printf
("%d", C[m]); } printf("\n"); } else for(i = 0; i < n; i++) { C[cur] = i; search(cur+1); } } int main() { scanf("%d", &n); search(0); printf("%d\n", tot); printf("%d\n", nc); return 0; }

C[i] == C[j] || i-C[i] == j-C[j] || i+C[i] == j+C[j]只比較列、主對角線、副對角線,原理見下圖:

輸入輸出:

普通回溯法(高效)

// n皇后問題:普通回溯法
// Rujia Liu

#include<cstdio>
using namespace std;

int C[50], tot = 0, n = 8, nc = 0;

void search(int cur) {
  int i, j;
  nc++;
  if(cur == n) { //遞迴邊界。 只要走到了這裡,所有皇后必然不衝突
    tot++;
  } else for(i = 0; i < n; i++) {
    int ok = 1;
	//嘗試把第cur行的皇后放在第i列
    C[cur] = i;
	//檢查是否和前面的皇后衝突
    for(j = 0; j < cur; j++)
      if(C[cur] == C[j] || cur-C[cur] == j-C[j] || cur+C[cur] == j+C[j]) {
        ok = 0;
        break;
      }
	//如果合法,則繼續遞迴
    if(ok) search(cur+1);
  }
}

int main() {
  scanf("%d", &n);
  search(0);
  printf("%d\n", tot);
  printf("%d\n", nc);
  return 0;
}

輸入輸出

4皇后執行了17次,比生成-測試法的341次,少了不少。

優化回溯法,用二維陣列判斷

// n皇后問題:優化的回溯法
// Rujia Liu

#include<cstdio>
#include<cstring>
using namespace std;

// vis表示列、主對角線、副對角線
int C[50], vis[3][50], tot = 0, n = 8, nc = 0;

void search(int cur) {
  int i, j;
  nc++;
  if(cur == n) {
    tot++;
  } else for(i = 0; i < n; i++) {
	//利用二維陣列直接判斷
    if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+n]) {
	  //如果不用列印解,整個C陣列都可以省略
      C[cur] = i;
	  //修改全域性變數
      vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = 1;
      search(cur+1);
	  //切記!一定要改回來
      vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = 0;
    }
  }
}

int main() {
  scanf("%d", &n);
  memset(vis, 0, sizeof(vis));
  search(0);
  printf("%d\n", tot);
  printf("%d\n", nc);
  return 0;
}

如果在回溯法中使用了輔助的全域性變數,一定要及時把他們恢復原狀。特別的,如果函式有多個出口,則需要在每個出口初恢復被修改的值。