深度优先搜索算法(Depth First Search,简称DFS):一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最糟糕的情况算法时间复杂度为O(!n)。

一、基本思想

  1. 为了求得问题的解,先选择某一种可能情况向前探索;
  2. 在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,继续向前探索;
  3. 如此反复进行,直至得到解或证明无解。

二、操作步骤:

  1. 初始原点为v0,使用深度优先搜索,首先访问 v0 -> v1 -> v2 -> v5,到 v5 后面没有结点,则回溯到 v1 ,即最近的且连接有没访问结点的结点v1
  2. 此次从 v1 出发,访问 v1 -> v4 -> v6 -> v3,此时与v3相连的两个结点 v0 与 v6 都已经访问过,回溯到 v6 (v6 具有没访问过的结点);
  3. 此次从 v6 出发,访问 v6 -> v7,到 v7 后面没有结点,回溯;
  4. 一直回溯到源点 v0 ,没有没访问过的结点,程序结束。

注:下面图中箭头为回溯方向

三、模板

C模板:

int a[510];   //存储每次选出来的数据
int book[510];   //标记是否被访问
int ans = 0; //记录符合条件的次数void DFS(int cur){if(cur == k){ //k个数已经选完,可以进行输出等相关操作 for(int i = 0; i < cur; i++){printf("%d ", a[i]);} ans++;return ;}for(int i = 0; i < n; i++){ //遍历 n个数,并从中选择k个数 if(!book[i]){ //若没有被访问 book[i] = 1; //标记已被访问 a[cur] = i;  //选定本数,并加入数组 DFS(cur + 1);  //递归,cur+1 book[i] = 0;  //释放,标记为没被访问,方便下次引用 }}
}

C++模板:

vector<int> a; // 记录每次排列
vector<int> book; //标记是否被访问 void DFS(int cur, int k, vector<int>& nums){if(cur == k){ //k个数已经选完,可以进行输出等相关操作 for(int i = 0; i < cur; i++){printf("%d ", a[i]);} return ;}for(int i = 0; i < k; i++){ //遍历 n个数,并从中选择k个数 if(book[nums[i]] == 0){ //若没有被访问a.push_back(nums[i]); //选定本输,并加入数组 book[nums[i]] = 1; //标记已被访问 DFS(cur + 1, n, nums); //递归,cur+1 book[nums[i]] = 0; //释放,标记为没被访问,方便下次引用 a.pop_back(); //弹出刚刚标记为未访问的数}}
}

四、例题

学算法当然要刷题领悟啦,不然就是我这种一看就会(只是背了下来),一写就废的菜鸡 ^ - ^
下面就让我们一起看看这个俗称不撞南墙不回头算法都有哪些例题!!!

1、排列问题

题目一:

设有n个整数的集合{1,2,…,n},从中取出任意r个数进行排列(1<=r<n<=10),试列出所有的排列。

示例:

输入:n = 4, r = 3
输出:
1 2 3
1 2 4
1 3 2
1 3 4
1 4 2
1 4 3
2 1 3
2 1 4
2 3 1
2 3 4
2 4 1
2 4 3
3 1 2
3 1 4
3 2 1
3 2 4
3 4 1
3 4 2
4 1 2
4 1 3
4 2 1
4 2 3
4 3 1
4 3 2
24

分析:

在这里某个元素按不同次序出现的组合应视为不同的排列。例如:1 2 3和2 1 3,元素均为1.2.3,只是排列顺序不同,因此应视为元素1.2.3的不同排列。

实现过程:

  1. 定义两个数组 a[] 与 book[] ,其中数组a保存每次的排列数据,数组book用来标记 i 这个数是否被访问;
  2. 初始化相关数据;
  3. 递归填数并判断第i个数填入是否合法:
    合法:填数,并判断是否已经到达环的终点。如果到达终点,打印结果;否则,继续填下一个数;
    不合法:选择下一种可能。

特别地,当n=r时,称为n的全排列。实现时只需把下面程序的终点改为cur==n即可。

AC代码:
#include<iostream>using namespace std;int n, r, ans;  //r个数进行全排列  ans为排列个数
int book[510]; //标记是否被访问
int a[510]; //记录每次的排列数据void DFS(int cur){ //从{1,2,...,n}中取r个数构成的排列if(cur == r){ //已经去够r个数 for(int i = 0; i < cur; i++){ //循环输出 cout << a[i] << ' ';}cout << endl;ans++;  //数量加1 return ;}for(int i = 1; i <= n; i++){  //循环遍历保证不漏 if(!book[i]){ //若没访问过 book[i] = 1; //标记已访问a[cur] = i; //i符合条件加入DFS(cur + 1); //寻找一个数字 book[i] = 0; //回溯:清除标记}}
} int main(){cin >> n >> r;DFS(0);cout << ans << endl;return 0;
}

题目二:

【LeetCode每日一题】46. 全排列 —— DFS算法(C/C++)

给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

分析:

具体分析与提交答案请点击题目,这里就不在一一赘述!!!

AC代码:
vector<vector<int>> ans; //记录答案
vector<int> a; // 记录每次排列
map<int, int> book; //标记是否被访问 void DFS(int cur, int n, vector<int>& nums){if(cur == n){ans.push_back(a);return ;}for(int i = 0; i < n; i++){if(book[nums[i]] == 0){a.push_back(nums[i]);book[nums[i]] = 1;DFS(cur + 1, n, nums);book[nums[i]] = 0;a.pop_back();}}
}vector<vector<int>> permute(vector<int>& nums) {int n = nums.size();DFS(0, n, nums);return ans;
}

题目三:

【LeetCode每日一题】784. 字母大小写全排列 —— DFS算法(C/C++)

给定一个字符串 s ,通过将字符串 s 中的每个字母转变大小写,我们可以获得一个新的字符串。

返回 所有可能得到的字符串集合 。以 任意顺序 返回输出。

示例 1:

输入:s = “a1b2”
输出:[“a1b2”, “a1B2”, “A1b2”, “A1B2”]

示例 2:

输入: s = “3z4”
输出: [“3z4”,“3Z4”]

提示:
1 <= s.length <= 12
s 由小写英文字母、大写英文字母和数字组成

分析:

具体思路方案与题目一差不多,这里我说一些需要用到的别的东西 ^ -^
在本题中首先使用 isdigit() 函数判断,若为数字则直接进行递归,即不用管;若为字母则使用 tolower() 函数——变为小写,然后递归,再使用 toupper() 函数——变为大写,递归。

若不明白 isdigit() 函数请看这篇:isdigit函数详解

AC代码:
vector<string> ans; //记录最终结果void DFS(int cur, string s){if(cur == s.size()){ans.push_back(s);return ;}if(isdigit(s[cur])){DFS(cur + 1, s); }else{s[cur] = tolower(s[cur]);DFS(cur + 1, s);s[cur] = toupper(s[cur]);DFS(cur + 1, s); }
} vector<string> letterCasePermutation(string s) {DFS(0, s);return ans;
}

2、组合问题

题目一:

【LeetCode每日一题】77. 组合 —— DFS算法(C/C++)
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 :

输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

提示:
1 <= n <= 20
1 <= k <= n

分析:

具体思路与排序差不多,具体需要注意的是,这里某种数字组合的多种排列视为相同情况,因此需 “去重”
一种可行的方案是填数的时候:

  1. 如果当前填的是第一个数,则直接填入;
  2. 在 1 的基础上,后面填入的数都要比前面的数大,因此要进行大小的比较。如果不符合条件,则不能填入。这样既能保证每种组合中数是递增的,也能保证组合是按字典序输出的。
AC代码:
vector<vector<int>> a; //存储排列数据
vector<int> b; // 存储每次的排列数据 void DFS(int cur, int n, int k){if(cur == k){a.push_back(b);return ; }for(int i = 1; i <= n; i++){int temp;if(cur > 0) temp = b.back();  //返回b数组的最后一个元素if((cur == 0) || (cur > 0 && i > temp)){ //第一个数或者后面的数大于前面的数b.push_back(i); //符合加入DFS(cur + 1, n, k);  //递归选择下一个数b.pop_back(); //弹出}}
}vector<vector<int>> combine(int n, int k) {DFS(0, n, k);return a;
}

3、n皇后问题

题目一:

洛谷——P1219 [USACO1.5]八皇后 Checker Challenge
一个如下的 6 * 6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。

上面的布局可以用序列 2 4 6 1 3 5 来描述,第 i 个数字表示在第 i 行的相应位置有一个棋子,如下:

行号 1 2 3 4 5 6
列号 2 4 6 1 3 5

这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 33 个解。最后一行是解的总个数。

输入格式:

一行一个正整数 n,表示棋盘是 n×n 大小的。

输出格式:

前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。

示例:

输入:6
输出:
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4

分析:

问题的关键在于如何判定某个皇后所在的行、列、斜线上是否有别的皇后;可以从矩阵的特点上找到规律,如果在同一行,则行号相同;如果在同一列上,则列号相同;如果同在/斜线上的行列值之和相同;如果同在\斜线上的行列值之差相同;从下图可验证:

在摆放皇后时,可以”按行摆放”(这样就保证了皇后不会横向攻击)。即:

(1)起点为 dfs(0),即从第0行开始摆放皇后,逐行进行。同时使用一维数组 map 保存第 cur 行的皇后摆放的列,也就是说每次尝试摆放皇后的位置坐标为 (cur, map[cur]);

(2)逐列遍历,若发现位置 (i, map[j]) 与位置 (cur, map[cur]) 在同一列 或 同一主对角线 或 同一副对角线上时,摆放失败,该方案”作废”,继续执行;

(3)若摆放成功,则 dfs(cur+1),表示继续摆放下一行,过程同上;

(4)当 cur=n,即n行皇后均摆放完成时,表示该方案可行,总方案数+1。

AC代码:
#include<iostream>using namespace std;const int M = 20;
int ans = 0, n;
int a[M]; //标记i行 纵坐标为a[i]void dfs(int cur){int flag = 1; //标记该序列是否可行if(cur == n){if(ans < 3){for(int i = 0; i < n-1; i++){cout << a[i] << " ";}cout << a[n-1] << endl;}ans++;return;} for(int i = 1; i <= n; i++){flag = 1;a[cur] = i;for(int j = 0; j < cur; j++){if(a[cur] == a[j] || cur+a[cur] == j+a[j] || cur-a[cur] == j-a[j]){flag = 0;break;}}if(flag == 1){dfs(cur+1);}}
}int main(){cin >> n;dfs(0);cout << ans << endl;return 0;
}

4、素数问题

素数问题有好多经典题型,例如素数环、素数和、和为素数等等;下面就来介绍几个经典例题,大家一起来学习吧。

题目一:

洛谷——P1036 [NOIP2002 普及组] 选数
已知 n 个整数 x1,x2,……,xn,以及 1 个整数 k(k<nk<n)。从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。例如当 n=4,k=3,4 个整数分别为 3,7,12,19 时,可得全部的组合与它们的和为:

3+7+12=22
3+7+19=29
7+12+19=38
3+12+19=34

现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数:3+7+19=29。

输入格式:

第一行两个空格隔开的整数 n,k(1≤n≤20,k<n)。
第二行 n 个整数,分别为 x1,x2,……,xn(1 ≤ xi ≤ 5*10^6)

输出格式:
输出一个整数,表示种类数。

示例:

输入:
4 3
3 7 12 19
输出:
1

分析:

本题是 dfs 中的一个非常经典的问题——素数问题中的一个分支,总体思路同上,都是循环遍历加判断 cur == k ;与上面不同的地方在于本类问题需要判断素数,下面我介绍一个判断素数的方法:

  1. 0,1 直接返回 false;
  2. 循环从 2 开始,根号下 x 结束,若 x % i 为 0 ,说明 x 有除 1 和它本身的其他因数,返回 false ;
AC代码:
#include<iostream>using namespace std;
int a[30], book[30];
int n, k, cnt = 0;bool Judge(int x){for(int i = 2; i*i <= x; i++){if(x % i == 0) return false;}return true;
}void dfs(int cur, int sum, int t){if(cur == k){if(Judge(sum)) cnt++;return;}for(int i = t; i < n; i++){dfs(cur+1, sum+a[i], i+1);}return;
}int main(){fill(book, book + 30, 0);cin >> n >> k;for(int i = 0; i < n; i++){cin >> a[i]; }dfs(0, 0, 0);cout << cnt << endl;return 0;
}

DFS (深度优先搜索) 算法详解 + 模板 + 例题,这一篇就够了相关推荐

  1. DFS(深度优先搜索)算法实现

    2 DFS算法 DFS(深度优先搜索)算法,搜索过程是类似于不撞南墙不回头的意思,DFS一般使用堆栈(先入后出)这种数据结构实现,由此一来,以初始起点为中心进行搜索,首先是周围点加入到堆栈中,起始点搜 ...

  2. 计算机基础ip地址私有地址,关于ip地址的详解,看完这篇就够了

    原标题:关于ip地址的详解,看完这篇就够了 前天我们发布了什么是公网ip?什么是内网ip?为什么ip地址通常以192.168开头?,有朋友反映有没有更基础的,那我们就从ip地址开始说起. 一.特殊的I ...

  3. linux top命令详解(看这一篇就够了)

    linux top命令详解(看这一篇就够了) top命令经常用来监控linux的系统状况,是常用的性能分析工具,能够实时显示系统中各个进程的资源占用情况. 常用参数 top的使用方式 top [-d ...

  4. DFS(深度优先搜索)详解(概念讲解,图片辅助,例题解释,剪枝技巧)

    目录 那年深夏 引入 1.什么是深度优先搜索(DFS)? 2.什么是栈? 3.什么是递归? 图解过程 问题示例 1.全排列问题 2.迷宫问题 3.棋盘问题(N皇后) 4.加法分解 模板 剪枝 1.简介 ...

  5. 【maven】最全Maven详解,看这一篇就够啦

    文章目录 一.引言 1.1 项目管理问题 1.1.1 繁琐 1.1.2 复杂 1.1.3 冗余 1.2 项目管理方案 二.介绍 三.Maven安装 3.1 下载Maven 3.2 Maven安装 3. ...

  6. 常用排序:冒泡排序与快速排序详解,看完这篇就够了!风马博客

    常用排序:冒泡排序与快速排序详解. 在排序算法中,冒泡排序和快速排序可以算是排序算法入门必会的两种排序了,今天和大家来分析一下如何快速理解并掌握这两种排序.首先冒泡排序是初学者最常用的排序,所以我们先 ...

  7. jvm与Tomcat调优【详解】——有这一篇就够了

    jvm与Tomcat调优 一.JVM性能调优 1.1 什么是JVM? 1.2 JVM调优工具 1.3 JVM调优经验 1.4常用JVM参数参考: 1.5 Java文件编译的过程 1.6 为什么说jav ...

  8. Linux 系统结构详解,看这一篇就够了?(又一篇万字长文)

    点击上方 "程序员小乐"关注, 星标或置顶一起成长 每天凌晨00点00分, 第一时间与你相约 每日英文 When one reaches a point of difficulty ...

  9. 深入浅出 万字详解 MyBatis看这一篇就够了!

    三层架构 界面层 Controller -> SpringMVC User interface layer,表示层,视图层,接受用户数据显示请求结果.使用web页面和用户交互,如jsp.html ...

最新文章

  1. PyTorch框架:(3)使用PyTorch框架构构建神经网络分类任务
  2. LeetCode 1242. Web Crawler Multithreaded--Java 解法--网路爬虫并发系列--ConcurrentHashMap/Collections.synchroni
  3. HDOJ 1214 圆桌会议
  4. SharePoint 2013 如何使用TaxonomyWebTaggingControl 控件
  5. 小程序循环不同的组建_小程序之八,对象数组、循环及条件渲染
  6. ReactJS入门之ReactJS简介
  7. 安卓手机 python控制_PyAndroidControl:使用python脚本控制你的安卓设备
  8. Properties 类的使用
  9. Linux系列之fdisk 分区挂盘
  10. matlab通用程序,三次样条差值-matlab通用程序
  11. NET环境下有关打印页面设置、打印机设置、打印预览对话框的实现-
  12. 第10章 随机山水画(《Python趣味创意编程》教学视频)
  13. Android 常用的adb命令
  14. Android Add new target
  15. 一分钟理解python里面的functools.partial
  16. Java二维码登录流程实现(包含短地址生成,含部分代码)
  17. (EXCEL VB初体验)EXCEL自动行高再加高,超详细
  18. <aop:aspectj-autoproxy />AOP自动代理
  19. 【RabbitMQ】RabbitMQ基础
  20. SpringBoot笔记通俗易懂版

热门文章

  1. 汽车无钥匙进入系统原理是什么
  2. Task2-爬虫-正则学习与实践(爬取天猫商品名称价格)
  3. vivoz5电池测试软件,5小时续航测试,vivo Z5x还剩多少电量?实测结果令人惊艳!...
  4. 实用!开关电源分类及开关电源测试解决方案
  5. Android性能优化:启动优化深入解析,助你全面掌握启动优化知识点
  6. 去除数组索引php,php如何删除数组索引
  7. ansys apdl建模案例2-----------齿轮
  8. 利用Nodejs实现爬虫
  9. chmod命令用法举例
  10. excel oledb mysql_通用Excel设置外部数据源引入Access数据库数据时,提示:“尚未注册 OLE DB 访问接口 Microsoft.Ace.Oledb.12.0”...