1. 程式人生 > 實用技巧 >【C/C++】n皇后問題/全排列/遞迴/回溯/演算法筆記4.3

【C/C++】n皇后問題/全排列/遞迴/回溯/演算法筆記4.3

按常規,先說一下我自己的理解。

遞迴中的return常用來作為遞迴終止的條件,但是對於返回數值的情況,要搞明白它是怎麼返回的。遞迴的方式就是自己呼叫自己,而在有返回值的函式中,上一層的函式還沒執行完就呼叫下一層,因此,當達到遞迴終止條件時,首先return的是最底層呼叫的函式,return之後,繼續執行上一層呼叫該函式之後的程式碼,此時我們看到的是上一層的情況,當上一層剩餘的程式碼執行完之後,表示上一層的函式也結束,此時再返回上上一層,執行遞迴程式碼之後的程式碼,如此往復迴圈,直到返回到最上層,結束整個遞迴過程。需要注意的是,上一層執行遞迴之後的程式碼的時候,會呼叫下一層返回的值,也可以理解為在執行上一層程式碼的時候會呼叫下一層的實現過程,直到下一層執行完返回一個數值,然後再加上上一層的數值,就構成了上一層return的東西,如此往復。

摘自CSDN

注意這個return,return是返回上一層,而不是跳出回到主函式。
然後如果不是return,在當前層沒有可以執行的東西的時候,也跳回到上一層。
下面我們來看演算法筆記中的全排列和n皇后問題。

n皇后問題

n皇后問題是指在一個n*n的國際象棋棋盤上放置n個皇后,使得這n個皇后兩兩均不在同一行、同一列、同一對角線上,求合法的方案數。
(我第一反應這不是圖論裡的匹配嘛……也可以點著色(x)
因為如果列舉n*n種情況的位置,選擇n個,計算量太大,所以我們只考慮全排列情況,然後剔掉不滿足不在同一對角線上的情況。

法1:列舉
這裡枚舉出n長數列的全排列,然後剔掉不滿足不在同一對角線上的情況。
全排列:給出1~n的數字,給出所有的排列方式(不重複)。(Ann)

遞迴思想:

  1. 遞迴邊界:
  2. 遞迴本體:假設已經填好了P[1]~P[index-1],正準備填P[index]. 列舉x從1到n,如果hashtable[x] == false,就把它填到index中。然後遞迴下一位。
    遞迴完成後,把這一位的hashtable[x]釋放。

判斷:到達邊界(輸出)的時候,判斷是不是在同一對角線上。

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 11;
int n;
int P[maxn];
bool hashTable[maxn] = {false};

int cnt = 0;

void show_hashTable()
{
   for (int i = 1; i <= n; i++)
   {
      printf("%d ", hashTable[i]);
   }
   printf("\n");
}

void generateP(int index)
{
   if(index == n+1) //遞迴邊界,邊界先判斷,只算結果
   {
      for(int i = 1; i <= n; i++)
      {
         printf("%d", P[i]);
      }
      printf("\n");
      return;
      // bool flag = true;  //flag為true表示當前排列合法
      // for(int i = 1; i <= n; i++)  //遍歷任意兩個皇后
      // {
      //    for(int j = i + 1; j <=n; j++)
      //    {
      //       if(abs(i - j) == abs(P[i] - P[j]))  //如果在一條對角線上
      //       {
      //          flag = false;  //不合法
      //       }
      //    }
      // }
      // if(flag) cnt++;
      // return;
   }
   for(int x = 1; x <= n; x++)
   {
      printf("x %d\n", x);
      if(hashTable[x] == false)
      {
         P[index] = x;
         //show_hashTable();
         hashTable[x] = true;
         generateP(index + 1);
         hashTable[x] = false;
         //show_hashTable();
      }
   }
   
}

int main()
{
   scanf("%d", &n);
   generateP(1);
   printf("%d\n", cnt);
   
   system("pause");
}

這段程式碼是怎麼執行的?
我實在是好奇,然後就一步一步嘗試了一下。
為了方便理解起見,這裡選n=3.
結果是123 132 213 231 312 321
自帶字典序。

 for(int x = 1; x <= n; x++)
   {
      printf("x %d\n", x);
      if(hashTable[x] == false)
      {
         P[index] = x;
         //show_hashTable();
         hashTable[x] = true;
         generateP(index + 1);
         hashTable[x] = false;
         //show_hashTable();
      }
   }

我做了一個這段程式碼的執行原理:

法2:回溯
定義:在到達遞迴前的某層,由於一些事實導致已經不需要往任何一個子問題遞迴,就可以直接返回上一層。
從程式碼編寫上來看,其實就是把判斷放到了遞迴的最開頭。

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 11;
int n;
int P[maxn];
bool hashTable[maxn] = {false};

int cnt = 0;

void show_hashTable()
{
   for (int i = 1; i <= n; i++)
   {
      printf("%d ", hashTable[i]);
   }
   printf("\n");
}

void generateP(int index)
{
   if(index == n+1) //遞迴邊界,邊界先判斷,只算結果
   {
      for(int i = 1; i <= n; i++)
      {
         printf("%d", P[i]);
      }
      printf("\n");
      cnt++;
      return;
      // bool flag = true;  //flag為true表示當前排列合法
      // for(int i = 1; i <= n; i++)  //遍歷任意兩個皇后
      // {
      //    for(int j = i + 1; j <=n; j++)
      //    {
      //       if(abs(i - j) == abs(P[i] - P[j]))  //如果在一條對角線上
      //       {
      //          flag = false;  //不合法
      //       }
      //    }
      // }
      // if(flag) cnt++;
      // return;
   }
   for(int x = 1; x <= n; x++)
   {
      //printf("x %d\n", x);
      if(hashTable[x] == false)
      {
         bool flag = true; //表示可行
         for (int pre = 1; pre < index; pre++) //考察index之前的是否會與index衝突
         {
            if (abs(index - pre) == abs(x - P[pre]))
            {
               flag = false;
               break; //已經設定了flag,很保險;break只是為了節約不要後面無意義的for迴圈
            }
         }
         if (flag)
         {
            P[index] = x;
            hashTable[x] = true;
            generateP(index + 1);
            hashTable[x] = false;
         }
      }

   }
   
}

int main()
{
   scanf("%d", &n);
   generateP(1);
   printf("%d\n", cnt);
   
   system("pause");
}