【位運算經典應用】 N皇后問題


說到位運算的經典應用,不得不說N皇后問題。

學過程序設計的都知道N皇后問題,沒聽過也沒關系。很簡單,最傳統的的N皇后問題是這個樣子的,給你一個n * n大小的board,讓你放n個皇后(國際象棋),要滿足任意兩個皇后不能在一條水平線上,不能在一條垂直線上,也不能在一條45度的斜線上。聽起來似乎和數獨挺像,其實N皇后的條件更苛刻,除了水平垂直線外,數獨只有兩條對角線,而N皇后有很多條斜線,這點需要注意。

為了判斷程序的正確性,正好leetcode上有道題可以測試N-Queens II ,也是最經典的N皇后問題,給出n求得擺法的數量。

常規解法###


一個很顯而易見的解法是遞歸求解,從上到下一行一行擺下去,並且記錄每行擺的位置,因為下一行擺放的位置要根據上面的擺放情況來確定。這里我們定義了一個pos數組,pos[1]的值表示擺在第一行的皇后所在的列,pos[2]的值表示擺在第二行的皇后所在的列,以此類推,所以擺到第n行時,上面擺放的皇后的位置就可以很容易地得到,從而可以進行判斷(該行什么位置可以擺放)。

當擺放到第r行時,我們遍歷該行所有的n個位置,判斷每個位置(r行c列)是否可以擺放,需要與前面擺放的每個皇后比較,是否在一條水平線上(這是不可能的),是否在一條垂直線上,是否在一條斜線上:

function check(r, c) {
  for (var i = 1; i < r; i++) {
    if (Math.abs(i - r) === Math.abs(pos[i] - c)) // 一條斜線上
      return true;

    if (c === pos[i]) // 一條豎直線上
      return true;
  }

  return false;
}

整個代碼也就顯而易見了:

var pos;

function check(r, c) {
  for (var i = 1; i < r; i++) {
    if (Math.abs(i - r) === Math.abs(pos[i] - c)) // 一條斜線上
      return true;

    if (c === pos[i]) // 一條豎直線上
      return true;
  }

  return false;
}

function dfs(r, n) {
  if (r === n + 1)
    return 1;

  var ans = 0;
  
  for (var i = 1; i <= n; i++) {
    if (check(r, i)) continue;
    pos[r] = i;
    ans += dfs(r + 1, n);
  }

  return ans;
}

var totalNQueens = function(n) {
  pos = [];
  return dfs(1, n);
};

位運算解法###


在讀下文前,請先閱讀【位運算經典應用】標志位與掩碼會事半功倍。

位運算解法,遞歸是避免不了的,能優化的在於check()函數部分。

假設一個4 * 4的board,我們在第一行第三列上擺了個皇后,其實它已經把第一行之后的6個位置給ko掉了:

- - o -
- X X X
X - X -
- - X -

我們用flag=2(1<<2)記錄第一行擺下的這個位置,在第二行中,很顯然(1<<2)這個位置已經不能擺了,而(2<<1)這個位置也不能擺,(2>>1)這個位置也不能擺。如果要在第二行右起第1個擺下皇后,我們用flag2=1(1<<1)記錄這個決定,我們只需用flag2和以上算出來的不能擺的位置去做個與運算,看看有沒有沖突即可。結果(2>>1)&(1<<1)得到了非0的數,表示兩者沖突,所以該位置擺放失敗。

假設我們接着在第二行左起第一個擺放了皇后,對於第三行的擺放來說,第一行擺的皇后對它還是有影響的,比如它不能擺在第三行左起第一個位置,因為(2<<2)&(1<<4)!==0,沖突。而(2<<2)正是在第二排擺放決策中(2<<1)<<1。看到這里你也許應該有了思路,沒錯,我們可以維護三個數,l, r, c, 用來表示該行被右上角斜線,左上角斜線,正上方直線所影響而不能擺放的位置。三個數每次與欲擺放的位置作與運算,求解是否沖突,如沒有,l和r分別左移右移后繼續遞歸。

function dfs(l, r, c, index, n) {
  if (index === n) 
    return 1;

  var ans = 0;

  for (var i = 0; i < n; i++) {
    var tmp = 1 << i;
    if ((tmp & l) || (tmp & r) || (tmp & c)) continue;
    ans += dfs((tmp | l) << 1, (tmp | r) >> 1, tmp | c, index + 1, n);
  }

  return ans;
}

var totalNQueens = function(n) {
  return dfs(0, 0, 0, 0, n);
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM