搜索与图论-DFS

简介: 搜索与图论-DFS

文章目录

一、DFS

1. DFS 简介

2. DFS 的实现步骤

3. DFS 实际演示

二、DFS 例题——排列数字

具体实现

1. 样例演示

2. 实现思路

3. 代码注解

4. 实现代码

三、DFS 例题—— n-皇后问题(经典)

具体实现——按行进行枚举

1. 样例演示

2. 实现思路

3. 代码注解

4. 实现代码

具体实现——按格子进行枚举

1. 实现思路

2. 实现代码


一、DFS

  • DFS 的关键点是递归和回溯。

1. DFS 简介


DFS(Depth First Search)是深度优先遍历,是图论当中一种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等。

其定义是,不断地沿着顶点的深度方向遍历。顶点的深度方向是指它的邻接点方向。

主要用于解决是否存在一个我们所需要的结果。因为 DFS 会首先把一种可能的情况尝试到底。才会回溯去尝试下一种情况,只要找到一种情况,就可以返回了。

DFS 问题一般分为两类:

(1) 定义的 DFS :对图的连通性进行测试,典型的问题:迷宫连通性测试、图的条件搜索等。

(2) 广义的 DFS–DFS 思路的应用: DFS 搜索顺序+规则问题、穷举结果寻求最优解/符合条件解等等,由于其穷举答案的本质,又被称为爆搜。


2. DFS 的实现步骤


  • (1) 从顶点出发。
  • (2) 访问顶点,也就是根节点。
  • (3) 依次从顶点的未被访问的邻接点出发,进行深度优先遍历;直至和顶点有路径相通的顶点都被访问。
  • (4) 若此时尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到所有顶点均被访问过为止。


3. DFS 实际演示

0c5a99cd356f4f2483dac6a52b180526.png



(1) 首从根节点 1 开始遍历,它相邻的节点有 2,3,4,先遍历节点 2,再遍历 2 的子节点 5,然后再遍历 5 的子节点 9。


35bcd7e809c4444c9fef3020b7cfd7f7.png


2) 上图中一条路已经走到底了( 9是叶子节点,再无可遍历的节点 ),此时就从 9 回退到上一个节点 5,看下节点 5 是否还有除 9 以外的节点,没有继续回退到 2,2 也没有除 5 以外的节点,回退到 1,1 有除 2 以外的节点 3,所以从节点 3 开始进行深度优先遍历,如下。


05c94f82e7e04325ad18169ee0dd4ce5.png


(3) 同理从 10 开始往上回溯到 6, 6 没有除 10 以外的子节点,再往上回溯,发现 3 有除 6 以外的子点 7,所以此时会遍历 7。

2d97d8e0ca694bfd98d13849523a4b63.png

  • (4) 从 7 往上回溯到 3, 1,发现 1 还有节点 4 未遍历,所以此时沿着 4, 8 进行遍历,这样就遍历完成了。
  • (5) 完整的节点的遍历顺序如下(节点上的的蓝色数字代表遍历的顺序)。


2b37ddea70d44a46ab48faa2d3edfb16.png



二、DFS 例题——排列数字

题目描述

给定一个整数 n,将数字 1∼n 排成一排,将会有很多种排列方法。

现在,请你按照字典序将所有的排列方法输出。

输入格式


共一行,包含一个整数 n。

输出格式

按字典序输出所有排列方案,每个方案占一行。

数据范围

1 ≤ n ≤ 7

输入样例


3

font size=5> 输出样例

1 2 3

1 3 2

2 1 3

2 3 1

3 1 2

3 2 1



具体实现

1. 样例演示



题目当中要求的是将数字 1∼n 排成一排,枚举每一种可能发送的情况。

最开始的时候,三个位置都是空的 _ _ _ 。

首先,填写第一个空位,第一个空位可以填 1 _ _ 。

填好第一个空位,填第二个空位,第二个空位可以填 2,填写后为:1 2 _ 。

填好第二个空位,填第三个空位,第三个空位可以填 3,填写后为: 1 2 3 。

此时,空位填完,无法继续填数,因此,这是第一种方案,输出该方案,然后进行回溯。

退到了状态:1 2 _ 。剩余第三个空位没有填数。第三个空位上除了填过的 3 ,没有其他数字可以填。


因此再往后退一步,退到了状态:1 _ _ 。第二个空位上除了填过的 2,还可以填 3 。第二个空位上填写 3,填写后为:1 3 _ 。

填好第二个空位,填第三个空位,第三个空位可以填 2,填写后为:1 3 2 。

这时候,空位填完,无法继续填数,所以这是第二种方案,输出该方案,然后进行回溯。

然后往后退一步,退到了状态:1 3 _ 。剩余第三个空位没有填数。第三个空位上除了填过的 2,没有其他数字可以填。

因此再往后退一步,退到了状态:1 _ _。第二个空位上除了填过的 2,3,没有其他数字可以填。


  • 因此再往后退一步,退到了状态:_ _ _。第一个空位上除了填过的 1,还可以填 2 和 3 。
  • 2 和 3 的填写方法跟 1 的填写方法相同,在此不做过多叙述,大家可以进行类比思考。
  • 整体流程如下图所示。


4dc38a17cf9841cf827a83900b7f3601.png


2. 实现思路

  • 详见样例演示。


3. 代码注解


st[N] 数组表示数字是否被用过,用 bool 类型对其进行表示,当其为 true 时表示被使用过,当其为 false 时,表示其没有被使用。

path[N] 数组保存排列。当排列的长度为 n 时,表明所有位置均已填充,为一种方案,对其进行输出。

dfs(i) 表示的含义是在 path[i] 处填写数字,然后递归的在下一个位置填写数字。

回溯是指第 i 个位置填写某个数字的所有情况都遍历后, 第 i 个位置填写下一个数字。


在完成以某一个数字为第一位置所有情况的输出时,要进行回溯,然后要注意将当下情况恢复为原始情况,即 state[N] 变为 false ,尚未使用。在此处,path[i] 不需要进行恢复原样,因为 path[i] 会被覆盖。


4. 实现代码

#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int n;
int path[N];
bool st[N];
void dfs(int u)
{
    if (u == n)
    {
        for (int i = 0; i < n; i ++ )
        {
            cout << path[i] << ' ' ;
        }
        cout << endl;
        return;
    }
    for (int i = 1; i <= n; i ++)
    {
        if (!st[i])  //判断这个数是否被使用过
        {
            path[u] = i;
            st[i] = true;
            dfs(u + 1);
            //path[i]不需要进行恢复原样,因为path[i]都会被覆盖。
            st[i] = false;
        }
    }
}
int main()
{
    cin >> n;
    dfs(0);
    system("pause");
    return 0;
}



三、DFS 例题—— n-皇后问题(经典)

题目描述


n − 皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。


4d2bcffc1a8a4b78b53cc3cdb2697485.png



现在给定整数 n,请你输出所有的满足条件的棋子摆法。


输入格式

共一行,包含整数 n。


输出格式

每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。

其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。

每个方案输出完成后,输出一个空行。


注意:行末不能有多余空格。

输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围

1 ≤ n ≤ 9

输入样例

4

输出样例


.Q…

…Q

Q…

…Q.

…Q.

Q…

…Q

.Q…

具体实现——按行进行枚举

1. 样例演示

  • 具体样例演示如下图:

04b322554f33407cbeac1e2bf47a6b5e.png

2. 实现思路


  • 首先,从第一行开始看,皇后可以放到哪一列。
  • 然后每一行依次进行枚举。
  • 注意:剪枝,就是提前判断,当前这个方案一定不合法时,直接将与这个方法相关的都完全排除掉。


3. 代码注解


ba036845c14441f3b956a03502816b4b.png


  • 因为 y-x 可能会是负数,但我们的数组下标不能是负数,所以,在此我们加上偏移量 n ,保证数组下标均为正数。

4. 实现代码


#include <bits/stdc++.h>
using namespace std;
const int N = 20;
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';
            col[i] = dg[u + i] = udg[n - u + i] = true;
            dfs(u + 1);
            col[i] = dg[u + i] = udg[n - u + i] = false;
            g[u][i] = '.';
        }
    }
}
int main()
{
    cin >> n;
    for(int i = 0; i < n; i ++)
    {
        for(int j = 0;j < n; j ++)
        {
            g[i][j] = '.';
        }
    }
    dfs(0);
    system("pause");
    return 0;
}


具体实现——按格子进行枚举

1. 实现思路


对每个格子进行枚举,每个格子都有放或不放两种情况,只要我们枚举到最后一个格子,就可以知道共有几种布局方案了。


cd593104ae7d499daf63cfd770d5e4ed.png



2. 实现代码

#include <bits/stdc++.h>
using namespace std;
const int N = 20;
int n;
char g[N][N];
bool row[N], col[N], dg[N * 2], udg[N * 2];
void dfs(int x,int y,int s)//x,y 表示坐标,u表示当前有几个皇后
{
    if (s > n) 
    {
        return;
    }
    if (y == n)
    {
        y = 0;
        x ++ ;
    }
    if (x == n)
    {
        if (s == n)
        {
            for (int i = 0; i < n; i ++ ) 
            {
                puts(g[i]);
            }
            puts("");
        }
        return;
    }
//不放皇后
    g[x][y] = '.';
    dfs(x, y + 1, s);
//放皇后
    if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n])
    {
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;
        g[x][y] = 'Q';
        dfs(x, y + 1, s + 1);
        g[x][y] = '.';
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = false;
    }
}
int main()
{
    cin >> n;
    for(int i = 0; i < n; i ++)
    {
        for(int j = 0;j < n; j ++)
        {
            g[i][j] = '.';
        }
    }
    dfs(0,0,0);
    system("pause");
    return 0;
}
















相关文章
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
人工智能 测试技术 API
探索通义灵码的无限可能:功能场景与应用实战分析
本文深入探讨了通义灵码在现代软件开发中的应用价值。通过代码补全、单元测试自动生成等功能,通义灵码显著提升了开发效率和代码质量。文章通过具体案例展示了其在团队协作、代码风格一致性和创新项目中的实用性,并展望了未来开发的新趋势。
686 5
探索通义灵码的无限可能:功能场景与应用实战分析
|
IDE Linux KVM
云计算|OpenStack|社区版OpenStack安装部署文档(十二--- openstack的网络模型解析---Rocky版)
云计算|OpenStack|社区版OpenStack安装部署文档(十二--- openstack的网络模型解析---Rocky版)
508 0
|
机器学习/深度学习 人工智能 自然语言处理
社区供稿 | 元象发布255B大规模MoE开源大模型,落地应用登顶港台榜
元象XVERSE发布 中国最大MoE开源模型:XVERSE-MoE-A36B,加速AI应用低成本部署,将国产开源提升至国际领先水平。
社区供稿 | 元象发布255B大规模MoE开源大模型,落地应用登顶港台榜
|
SQL 缓存 监控
接口性能倍增记:一次成功的优化实践
在软件开发过程中,接口性能优化是提升用户体验和系统稳定性的关键环节。本文将分享一次接口优化的成功案例,从问题发现到解决方案实施,详细介绍我们的优化过程和成果。
296 0
|
传感器 算法
基于MPPT的风力机发电系统simulink建模与仿真
本课题基于最大功率点跟踪(MPPT)技术,对风力机发电系统进行Simulink建模与仿真。通过S函数实现MPPT算法,实时监测和调整风力发电机的工作状态,使其始终工作在最佳效率点,从而最大限度地利用风能,提高风力发电效率。系统包括风速传感器、发电机状态监测模块、MPPT控制器、发电机驱动系统及反馈回路,确保闭环控制的稳定性和准确性。
|
存储 供应链 安全
web3.0知识扫盲
web3.0知识扫盲
590 0
|
网络协议 安全 数据挖掘
F5《企业DNS建设白皮书》中的DNS解析服务器最佳实践
F5《企业DNS建设白皮书》中的DNS解析服务器最佳实践
416 0
F5《企业DNS建设白皮书》中的DNS解析服务器最佳实践
Dart中的String类型定义与拼接
Dart中的String类型定义与拼接
253 0
|
存储 自然语言处理 算法
Tokenization 指南:字节对编码,WordPiece等方法Python代码详解
在2022年11月OpenAI的ChatGPT发布之后,大型语言模型(llm)变得非常受欢迎。从那时起,这些语言模型的使用得到了爆炸式的发展,这在一定程度上得益于HuggingFace的Transformer库和PyTorch等库。
442 3