(新手向)N皇后問題詳解(DFS算法)


非常經典的一道題:

N皇后問題: 
國際象棋中皇后的勢力范圍覆蓋其所在的行、列以及兩條對角線,現在考察如下問題:如何在n x n的棋盤上放置n個皇后,使得她們彼此互不攻擊 。

免去麻煩我們這里假定n不是很大。。

 

 

 (圖片來自百度百科(這是8皇后問題的一種解法))

某leetcode大犇曾說過:“這個問題和解數獨題目有一個很大的共同點,那就是:我都不會。”

好了下面開始分析:(廢話警告)

初步判斷這問題的特點有:

1.有個場地來放置單位。

2.各個單位之間有制約。

3.沒有特殊的數學方法,得把某一個擺法擺出來才能判斷是否可行。

於是萌新一般都會這么想:對於1,:我搞個二維數組來存。對於2:我搞個判定函數來一個一個判定。對於3:我暴力枚舉。

那么算法框架大概就是:我對二維數組的所有情況進行枚舉,然后對每種情況進行判定

over ,輸個數字n,按下回車,雙手離開鍵盤,等了老半天發現命令提示符只有個光標在跳動 |

如果是在做網題,說不定就判定是超時或者內存溢出,過不了。

然后就開始思考怎么優化:

對於1,我可以縮減嗎····,改成一維數組,,,似乎徒增麻煩。

對於2,我可以有更巧妙的判斷方法嗎?·············有個鬼,還不得把每個棋子的每一行每一列每兩個斜線都瞅一下。

對於3,我一定要把所有情況都舉出來嘛?···(於是腦子里面開始擺起了棋子,模擬算法過程)

                    然后就會發現  比如第一行前兩個格子一開始就擺了兩個相鄰的棋子,誒這不明擺着皇后互懟了嗎!后面還繼續枚舉就太傻了吧···

     這種傻事情做了不是白白增加耗時嗎!

      所以就思考如何在這種“傻情況”出現的時候就pass掉后面所有情況。。。

於是腦子陷入了一團亂麻。。。

打住!

咱們換個思考方式,既然直接想有點困難,不如我們想辦法先做點處理,讓這些玩意兒便於我們把弄。

再回頭想想規則,皇后可以吃掉所處的一行一列以及斜線上的棋子,那也就是說,每一行都最多只能有一個棋子,每一列都最多只能有一個棋子,

那我們不妨把每一行看成一組!

那么這一組就只有n個可能性(一行n個位置 每一次只有一個位置被占用)

也就是單個一行有n種可能性

因為有n行

哈哈那么就瞬間只需要討論n*n種可能性了!

 還記得我們之前的思考嗎?:

對於3,我一定要把所有情況都舉出來嘛?···(於是腦子里面開始擺起了棋子,模擬算法過程)

     然后就會發現  比如第一行前兩個格子一開始就擺了兩個相鄰的棋子,誒這不明擺着皇后互懟了嗎!后面還繼續枚舉就太傻了吧···

   這種傻事情做了不是白白增加耗時嗎!

   所以就思考如何在這種“傻情況”出現的時候就pass掉后面所有情況。。。

像這種情況:

(我的天這皇后畫的...)

 

顯然下面3,4,5····行都不用枚舉了,直接pass掉

於是我們思路慢慢清晰了起來:

我們從第一行開始枚舉,第一行第一格:

 

 

然后枚舉第二行,也從第一格開始:

判定一下,哇,不行,咋辦呢?這情況下面所有行都沒有枚舉的必要了,但是本行還是可以繼續的。。。於是我們開始枚舉第二行第二個情況:

 

 不行(斜線上互吃)。

繼續第二行下一個情況:

 

 

 

行嘞!

那么我們就可以開第三行了:

 

不行(一列上吃)。

 

不行(斜線上吃)

 

 

 

 

 不行。

 

 

 

 還不行

 

 

 

 行嘞!

 

··················

好了,算法思路大概就出來了。

 我們就這樣干下去,直到最后一行可行,那么我們就獲得了一個可行解了,

然后我們想繼續獲得其他解,那么繼續枚舉下去,但是要退一步:

先從倒數第一行開始,我們枚舉下一個,有解則輸出,無解則繼續。

倒數第一行結束了,咋辦?

別忘了倒數第二行的情況還不一定枚舉完了呢。

於是退到倒數第二行,繼續如上操作。

··················


干說着沒用,擼代碼:

#include<iostream>

using namespace std;
int main()
{
    return 0;
}

先准備好需要用的存儲(這里用64個綽綽有余)

ans是用來存每一次的解 ans[1]表示第一行的那個元素的所在列的位置

 1 #include<iostream>
 2 
 3 using namespace std;  4 bool colMark[64] = { 0 };//元素所在列 如果有了元素在第k列 那么colMark[k]就標記上true  5 bool naMark[64] = { 0 };//捺,撇···顧名思義,表示元素所在的兩個斜線o.o 原理同上  6 bool pieMark[64] = { 0 };  7 int total = 0; //解的總個數  8 int N = 0;  9 int ans[64] = { 0 }; 10 int main()
11 {
12     return 0;
13 }

然后下面先寫一個顯示函數:

1 void showOneSolution()//用來顯示一個解
2 {
3     total++; 
4     for (int i = 1; i<=N; ++i)
5     {
6         cout << ans[i] << " ";
7     }
8         cout << endl;
9 }

下面是核心:

void Dfs(int i)   //可以百度學習 深度優先搜索
{
    if (i > N)              //判斷是否已經枚舉完了N行 
    {
        showOneSolution();  //枚舉完了就輸出(此刻我們處於N+1行)
        return;             //返回到第N行
    }
    for (int j = 1; j <= N; ++j) //這里的j表示  本行的第j列
    {
        if ((!colMark[j])&&(!naMark[j-i+N])&&(!pieMark[i+j]))    //這里的"j-i+N"  "i+j"建議自己體會
        {          //判斷這個格子所在的兩個斜線和所在列是否為空
            colMark[j] = true;
            naMark[j - i + N] = true;
            pieMark[i + j] = true;
            ans[i] = j;
            Dfs(i + 1);
            colMark[j] = false;      //這個格子枚舉完了要進入本行第j+1個格子(或者結束本行) 走之前別忘了恢復標記
            naMark[j - i + N] = false;
            pieMark[i + j] = false;
        }
    }
}

最后組裝起來:(當然啦,還是建議自己擼代碼,你會發現很多意想不到的問題~解決問題的同時也是進步)

 1 #include<iostream>
 2 
 3
 4 using namespace std;
 5 bool colMark[64] = { 0 };
 6 bool naMark[64] = { 0 };
 7 bool pieMark[64] = { 0 };
 8 int total = 0;
 9 int N = 0;
10 int ans[64] = { 0 };
11 void showOneSolution()//用來顯示一個解
12 {
13     total++;
14     for (int i = 1; i<=N; ++i)
15     {
16         cout << ans[i] << " ";
17     }
18         cout << endl;
19 }
20 void Dfs(int i) 
21 {
22     if (i > N)              //判斷是否已經枚舉完了N行 
23     {
24         showOneSolution();  //枚舉完了就輸出(此刻我們處於N+1行)
25         return;             //返回到第N行
26     }
27     for (int j = 1; j <= N; ++j) //這里的j表示  本行的第j列
28     {
29         if ((!colMark[j]) && (!naMark[j - i + N]) && (!pieMark[i + j])) 
30         {          //判斷這個格子所在的兩個斜線和所在列是否為空
31             colMark[j] = true;
32             naMark[j - i + N] = true;
33             pieMark[i + j] = true;
34             ans[i] = j;
35             Dfs(i + 1);
36             colMark[j] = false;      // 走之前別忘了恢復標記
37             naMark[j - i + N] = false;
38             pieMark[i + j] = false;
39         }
40     }
41 }
42 int main()
43 {
44     cin >> N;
45     Dfs(1);
46     cout << total;
47     system("pause");
48     return 0;
49 }

總結:

思路混亂不妨換個角度理一理。

在紙上面畫一畫算法過程會有不少幫助。

代碼少出錯還是得多擼。

想什么呢?!擼代碼!


免責聲明!

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



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