【第十八课】DFS:深度优先搜索( acwing-843 n-皇后问题 / 两种搜索思路 / c++代码 )

文章讲述了作者在解决八皇后问题时,初始的错误思路和优化后的DFS剪枝方法。重点介绍了如何处理输入输出格式的转换,以及如何通过剪枝避免不符合题意的解法,最终实现对皇后问题的正确求解。
摘要由CSDN通过智能技术生成

目录

目录

错误写法(可跳

DFS-剪枝

代码

思路二: 原始解法

代码如下

代码解释 


错误写法(可跳

看到这道题,我想这不还是n个数的全排列的问题么?也就是把数字变成了字符,一些输出格式上的变化。于是就在原有代码上修改一下应该就行。

我的思路就还是path存有可能的排序路径,但是输出的时候要输出字符,且为棋盘格的二维数组形式,因此添加了两层for循环嵌套,并用if语句判断 path[i]==j ,说明此处放皇后Q,符合输出格式。

代码

#include<iostream>
using namespace std;
const int N=12;
int n;
int path[N];
bool st[N];

void dfs(int x)
{
    if(x==n)
    {
        for(int i=0;i<n-1;i++)//只判断了相邻两个皇后是否处于同一斜线
        {//由于数组会索引到i+1 因此第二个表达式应该是i<n-1
            if(path[i]-path[i+1]==1 || path[i]-path[i+1]==-1)return;
        }
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(path[i]==j)
                {
                    printf("Q");
                }
                else{
                    printf(".");
                }
            }
            puts("");
        }
        puts("");
        return;
    }

    for(int i=0;i<n;i++)
    {
        if(!st[i])
        {
            st[i]=1;
            path[x]=i;
            dfs(x+1);
            st[i]=0;
        }
    }
}
int main()
{
    scanf("%d",&n);

    dfs(0);

    return 0;
}

这里错误的原因在于,我通过在得到一种答案输出之前,

for(int i=0;i<n-1;i++)//只判断了相邻两个皇后是否处于同一斜线
    {
        if(path[i]-path[i+1]==1 || path[i]-path[i+1]==-1)return;
    }

判断相邻两个皇后的数字相差不能是1或-1,也就是限制了相邻两个皇后的位置不能是对角线或者副对角线的关系。

但是题目要求的是任何两个皇后都不能呈对角线或副对角线的关系。因此这样的写法是不符合题意的虽然有些情况下会输出正确结果,但是正确率极低。只能说是忘记模板的话能蹭一点点分😂。

ok正文开始~

DFS-剪枝

我们观察到这是一个在 n个数的全排列 框架之下的问题。我们需要注意的就是

1.输入输出格式是字符因此我们考虑把原来的path存储答案的序列改为 char 类型的 g[N][N]  二维数组,也方便符合条件的输入输出。

首先要将二维棋盘格初始化全为 '.' ,利用两层for循环嵌套。输出的时候可以直接输出 g[i] 表示输出一行的字符串

其次在dfs恢复现场的时候, g[u][i]='.' 也要恢复。因为我们在搜索过程中修改了棋盘的状态。当我们从一个节点回溯到它的父节点时,我们需要把棋盘恢复到父节点的状态,以便于搜索父节点的其他子节点。

在单纯的数字全排列问题中我们会不断地在同一位置尝试不同的数字,因此前一个数字会被后一个数字覆盖。这个过程并不需要我们手动去恢复,因为每次尝试新的数字时,旧的数字自然就被"覆盖"了

2.任意两个皇后都不能处于同一行、同一列或同一斜线上

这是区别于模板的最主要的题设。有了这些限制,我们就要多加限制条件,去掉一些不满足条件的答案,称“剪枝”。

由于我们是以行为基准,每一次递归调用dfs(u+1)都会使得行数u增加1,这样就保证了每一行只有一个皇后。因此,我们并不需要额外的数组来检查是否有多个皇后在同一行。我们需要另外定义col数组表示列,dg数组表示对角线,udg数组表示主对角线。 

col数组的坐标直接是 i  表示列,那么如何表示对角线呢

在一个二维平面上,一条直线的斜率和截距可以唯一确定这条直线。在这个问题中,我们的直线其实就是棋盘上的对角线,斜率固定为1或-1(因为对角线的斜率是固定的),所以我们只需要找到一个唯一的截距就可以确定一条对角线

代码中u就表示行,i 表示列。由此得出dg udg 的下标表示 

for(int i=0;i<n;i++)
    {
        if(!col[i] && !dg[u+i] && !udg[n-u+i])
        {
            g[u][i]='Q';//用u来表示行,变化的是第u行中的某个元素
            col[i]=dg[u+i]=udg[n-u+i]=1;
            dfs(u+1);//通过递归确保了每一行放一个皇后
            g[u][i]='.';//恢复现场
            col[i]=dg[u+i]=udg[n-u+i]=0;
        }
    }

代码

#include<iostream>
using namespace std;
const int N=12;
int n;
char g[N][N];
bool col[N],dg[N],udg[N];

void dfs(int u)
{
    if(u==n)
    {
        for(int i=0;i<n;i++)
        {
            puts(g[i]);
        }
        puts("");
        return;
    }
    for(int i=0;i<n;i++)
    {
        if(!col[i] && !dg[u+i] && !udg[n-u+i])
        {
            g[u][i]='Q';//用u来表示行,变化的是第u行中的某个元素
            col[i]=dg[u+i]=udg[n-u+i]=1;
            dfs(u+1);//通过递归确保了每一行放一个皇后
            g[u][i]='.';//恢复现场
            col[i]=dg[u+i]=udg[n-u+i]=0;
        }
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            g[i][j]='.';
        }
    }
    dfs(0);
    return 0;
}

思路二: 原始解法

我们可以有另一种更为原始的方式搜索,就是一个格子一个格子枚举,对于每个格子我我们都有放皇后和不放皇后这两种选择。 

这是这种解法的搜索思路。

代码如下

#include <iostream>
using namespace std;
const int N = 12;
int n;
char g[N][N];
bool row[N], col[N], dg[N], udg[N];

void dfs(int x, int y, int s)
{
    if (y == n) // 该行已经遍历完了,因此列标要重新从0开始,行数+1
    {
        y = 0;
        x++;
    }
    if (x == n) // 遍历到了最后一行
    {
        if (s == n) // 如果皇后数量也足够的话,说明找到了一个答案
        {
            for (int i = 0; i < n; i++)
            {
                puts(g[i]);
            }
            puts("");
        }
        return;
    }

    // 不放皇后,就直接到下一个格子也就是下一列y+1
    dfs(x, y + 1, s);

    // 如果满足这些条件会放皇后
    if (!row[x] && !col[y] && !dg[x + y] && !udg[y - x + n])
    {
        g[x][y] = 'Q';
        row[x] = col[y] = dg[x + y] = udg[y - x + n] = 1;
        dfs(x, y + 1, s + 1);
        row[x] = col[y] = dg[x + y] = udg[y - x + n] = 0;
        g[x][y] = '.';
    }
}
int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            g[i][j] = '.';
        }
    }
    dfs(0, 0, 0); // 从第0行第0列即左上角开始搜,此时已经放过的皇后数量为0

    return 0;
}

代码解释 

这里我疑惑了很久的就是关于不放皇后的dfs递归函数上,我不理解递归一直放在那,那岂不是每次调用递归函数总是会先执行又一次递归,不断地深入递归,下面的放皇后的if语句到底什么时候、怎么执行呢?什么时候有执行的机会呢?

!!!!!

关于这里,其实是,我们确实是用dfs递归函数先遍历一下棋盘格的所有位置,都先默认不放皇后,然后到达递归最深层,之后再一层层返回倒着遍历完整个棋盘格 

我有种豁然开朗的感觉hh,这就是主要需要理解的部分!!

可是其实我当时还不太理解到底什么时候是递归的最深层?

递归的最深层,其实也算是说递归的出口了。豁一下又清晰了好多hh

在dfs函数中,在放不放皇后之前,有两个if语句,第一个y=n的,帮我们限制了每一列结束时不发生越界。x=n,帮我们限制了每一列结束时不发生越界,同时也蕴含递归的出口

这里递归的出口其实就是看哪里写了return,即在x=n时,不管是否输出答案,都会执行return 。这里注意我们的x=n y=n都是我们越界的值,因为我们是从第0行开始遍历的,因此我们遍历到n-1的时候,就是数量上的n。 !!!(这里也要注意,因为我忘记了这个然后又困惑了好一会hh)

好啦,看到这里应该没什么问题了。这道题的代码也很容易理解了。

果然最不好理解的还是递归hhh

关于代码中有个点:就是udg数组表示的是主对角线,主对角线的下标我们上面说过应该是 n-x+y

这里我提一下,“udg[y-x + n]”和“udg[x-y + n]”都可以正确表示主对角线,因为它们实际上代表了相同的对角线,是等价的。


先写到这咯,状态不太好,下午先休息了emmm

有问题欢迎指出!!一起加油!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值