(For beginners) Detailed Explanation of N Queens Problem (DFS Algorithm)

A very classic question:

N Queens Problem: 
The sphere of influence of a queen in chess covers its row, column and two diagonals. Now consider the following problem: how to place n queens on an nxn board so that they do not attack each other.

To save trouble we assume here that n is not very large. .

(The picture is from Baidu Encyclopedia (this is a solution to the 8 queens problem))

A certain leetcode Daben once said: "This problem and the Sudoku problem have one thing in common, that is: I don't know it."

Ok, let's start the analysis: (nonsense warning)

The characteristics of the preliminary judgment of this problem are:

1. There is a field to place units.

2. There are constraints among various units.

3. There is no special mathematical method, you have to put out a certain arrangement to judge whether it is feasible.

So Mengxin generally thinks this way: For 1,: I will make a two-dimensional array to store it. For 2: I make a judgment function to judge one by one. For 3: I brute force enumeration.

Then the algorithm framework is roughly: I enumerate all the cases of the two-dimensional array, and then judge each case

over , enter a number n, press Enter, leave the keyboard with both hands, and wait for a long time to find that the command prompt only has a cursor beating |

If you are doing an online question, it may be judged that it is a timeout or memory overflow, and you will not be able to pass it.

Then start thinking about how to optimize:

For 1, can I reduce it..., change it to a one-dimensional array,,, it seems to increase the trouble.

For 2, can I have a smarter way of judging? ··············································································································································································································································——

For 3, do I have to list all the cases? ···(So I started to place chess pieces in my mind to simulate the algorithm process)

Then you will find that, for example, two adjacent chess pieces are placed in the first two grids of the first row. Hey, it’s not clear that the queens are fighting each other! It would be too silly to continue to enumerate later...

Isn't it time-consuming to do such a silly thing in vain!

So just think about how to pass all the following situations when this "stupid situation" occurs. . .

So my mind was in a mess. . .

stop!

Let's change the way of thinking. Since it is difficult to think directly, why don't we find a way to deal with it first, so that these things are easy for us to handle.

Thinking back to the rules, the queen can eat the chess pieces on the row, column and diagonal line, that is to say, each row can only have at most one chess piece, and each column can only have at most one chess piece.

Then we might as well treat each row as a group!

Then there are only n possibilities in this group (only one position of n positions in a row is occupied at a time)

That is, there are n possibilities for a single row

because there are n rows

Haha, then only n*n possibilities need to be discussed in an instant!

 Remember what we were thinking about earlier? :

对于3,我一定要把所有情况都举出来嘛?···(于是脑子里面开始摆起了棋子,模拟算法过程)

     然后就会发现  比如第一行前两个格子一开始就摆了两个相邻的棋子,诶这不明摆着皇后互怼了吗!后面还继续枚举就太傻了吧···

   这种傻事情做了不是白白增加耗时吗!

   所以就思考如何在这种“傻情况”出现的时候就pass掉后面所有情况。。。

Like this situation:

(My God this queen drew...)

Obviously, the following 3, 4, 5... lines do not need to be enumerated, just pass them directly

So our thinking gradually became clear:

We start enumerating from the first row, the first row and the first cell:

Then enumerate the second row, also starting from the first cell:

 Make a judgment, wow, no, what should I do? In this case, there is no need to enumerate all the following lines, but this line can still continue. . . So we start to enumerate the second case in the second row:

No way (cannibalize each other on the slash).

Continue to the next case on the second line:

OK!

Then we can open the third line:

No way (eat on one column).

No (eat on a slash)

 no.

 not yet

 OK!

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

Well, the algorithm idea probably came out.

 Let's go on like this until the last line is feasible, then we have a feasible solution,

Then we want to continue to obtain other solutions, so continue to enumerate, but take a step back:

Starting from the penultimate line, we enumerate the next one, output if there is a solution, and continue if there is no solution.

The penultimate line is over, what should I do?

Don't forget that the case of the penultimate line is not necessarily enumerated yet.

So go back to the penultimate line and continue the above operation.

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


 It's useless to talk, just roll the code:

#include<iostream>

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

Prepare the storage you need first (64 are more than enough here)

ans is used to store each solution ans[1] indicates the column position of the element in the first row

#include<iostream>

using namespace std;
bool colMark[64] = { 0 };//元素所在列  如果有了元素在第k列 那么colMark[k]就标记上true
bool naMark[64] = { 0 };//捺,撇···顾名思义,表示元素所在的两个斜线o.o 原理同上
bool pieMark[64] = { 0 };
int total = 0;           //解的总个数
int N = 0;
int ans[64] = { 0 };
int main()
{
    return 0;
}

Then write a display function first: 

void showOneSolution()//用来显示一个解
{
    total++;
    for (int i = 1; i<=N; ++i)
    {
        cout << ans[i] << " ";
    }
        cout << endl;
}

Here is the core:

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;
        }
    }
}

 Finally assemble it: (Of course, it is recommended to write the code yourself, you will find many unexpected problems ~ solving problems is also progress)

#include<iostream>


using namespace std;
bool colMark[64] = { 0 };
bool naMark[64] = { 0 };
bool pieMark[64] = { 0 };
int total = 0;
int N = 0;
int ans[64] = { 0 };
void showOneSolution()//用来显示一个解
{
    total++;
    for (int i = 1; i<=N; ++i)
    {
        cout << ans[i] << " ";
    }
        cout << endl;
}
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]))
        {          //判断这个格子所在的两个斜线和所在列是否为空
            colMark[j] = true;
            naMark[j - i + N] = true;
            pieMark[i + j] = true;
            ans[i] = j;
            Dfs(i + 1);
            colMark[j] = false;      // 走之前别忘了恢复标记
            naMark[j - i + N] = false;
            pieMark[i + j] = false;
        }
    }
}
int main()
{
    cin >> N;
    Dfs(1);
    cout << total;
    system("pause");
    return 0;
}

Summarize:

If you are confused, you might as well try to think about it from another angle.

Drawing a picture of the algorithm process on paper will help a lot.

The code is less error-prone or more messed up.

What are you thinking? ! Roll the code!

Guess you like

Origin blog.csdn.net/HowToPause/article/details/127319625