1

蓝桥杯算法竞赛系列第五章——拔高篇之深度优先搜索(DFS)

 2 years ago
source link: https://blog.csdn.net/weixin_57544072/article/details/121262172
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

欢迎回到:遇见蓝桥遇见你,不负代码不负卿! 

目录

一、引入:深度优先搜索(DFS) 

二、经典例题

例题1.二叉搜索树的范围和

题目描述

题解

代码执行

例题2.岛屿数量 

题目描述

题解

代码执行

例题3.背包问题

题目描述

题解

代码执行

三、思考题

四、蓝桥结语:遇见蓝桥遇见你,不负代码不负卿!


提到深度优先搜索(DFS),我们很容易就会想到广度优先搜索(BFS),它们俩合在一起称为一个搜索专题,今天笔者先把DFS讲清楚,BFS的内容留在下一章详细讲解。

OK,废话不多说,走着...先送你一朵小红花...

一、引入:深度优先搜索(DFS) 

这块内容很重要哦,为了方便大家理解,先举一个(来自胡凡、曾磊老师编写的《算法笔记》一书)的栗子。

举个栗子:

设想我们现在以第一视角身处一个巨大的迷宫当中,没有上帝视角,没有通信设施,更没有热血动漫里的奇迹,有的只是四周长得一样的墙壁。于是我们只能自己想办法走出去。如果迷失了内心,随便乱走,那么很可能会被四周完全相同的景色绕晕在其中,这时只能放弃所谓的侥幸,而去采取下面这种看上去很盲目但实际上会很有效的方法。

以当前所在位置为起点,沿着一条路向前走,当碰到岔道口时,选择其中一条岔道前进。如果岔路中存在新的岔道口,那么仍然按照上面的方法枚举新岔道口的每一条岔路。这样,只要迷宫存在出口,那么这个方法一定能找到它。

可能有铁汁会问,如果在第一个岔道口选择了没有出路的分支,而这个分支比较深,并且路上多次出现了新的岔道口,那么当发现这个分支是个死分支之后,如何退回到最初的这个岔道口?其实方法很简单,只要让自己的右手,始终贴着右边的墙壁一路往前走,那么自动会执行上面这个走法,并且最终一定能找到出口。

你看下图:

由图可知,从起点开始前进,当碰到岔道口时,总是选择其中一条岔路前进(注意哦,这里总是选择最右手边的岔路),在岔路上如果又遇到新的岔道口,仍然选择新岔道口的其中一条岔路前进,直到碰到死胡同才回退到最近的岔道口选择另一条岔路。也就是说,当碰到岔道口时,总是以“深度”作为前进的关键词,不碰到死胡同就不回头,因此把这种搜索的方式称为深度优先搜索。 

从这个迷宫的例子还可以注意到,深度优先搜索会走遍所有路径,并且每次走到死胡同就代表一条完整路径的形成。这就是说,深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法。

总结何为深度优先搜索:

从根节点开始,尽可能深的搜索每一个分支,把一个分支的结果搜索完,再去看另一个分支。形象来说:“一条路走到底,不撞南墙不回头”。

【敲黑板】:上面的内容可能有点绕,所以建议铁汁们最好认真看个三遍这样子,好好去理解,毕竟这一章属于拔高部分,可能稍微难理解些,但做题目后会发现其实不难,不信的话,往后看。不对不对,先把上面弄得差不多了再朝后看...

问:用什么方法能更好的实现深度优先搜索呢?

其实要想既容易理解又容易实现深度优先搜索非递归莫属 

这里就不用俺再强调递归的重要了吧,后面很多地方都要用到递归,尤其是数据结构中的二叉树。

这里再温习一下上篇的递归部分:

蓝桥杯算法竞赛系列第二章——深入理解重难点之递归(上)_安然无虞的博客-CSDN博客

回顾一下对于斐波那契数列的定义:F(0) = 1, F(1) = 1, F(n) = F(n - 1) + F(n - 2) (n >= 2)。可以从这个定义挖掘到,每当将F(n)分为两部分F(n - 1)与F(n - 2)时,就可以把F(n)看做迷宫的岔道口,由它可以到达两个新的关键节点F(n - 1) 和 F(n - 2)。而之后计算F(n - 1)时,又可以把F(n - 1)当作在岔道口F(n)之下的岔道口。

既然有岔道口,那么一定有死胡同。很容易想象,当访问到F(0)和F(1)时,就无法向下递归下去,因此F(0)、F(1)在这里就是死胡同。这样说来,递归中的递归式就是岔道口,而递归边界就是死胡同,这样一来,就可以把如何用递归实现深度优先搜索的过程理解的很清楚。

因此,使用递归可以很好地实现深度优先搜索。这个说法并不是说深度优先搜索就是递归,只能说深度优先搜索是递归的一种实现方式,因为使用非递归也能实现DFS的思想,但是一般情况下会比递归麻烦。不过,使用递归时,系统会调用一个叫系统栈的东西来存放递归中每一层的状态,因此使用递归来实现DFS的本质其实还是栈。

DFS主要应用场景:

  • 二叉树搜索

 好咯,前面简单引入了一下DFS,下面准备要上硬菜咯...

二、经典例题

例题1.二叉搜索树的范围和

给定二叉搜索树的根结点 root,返回值位于范围 [low, high] 之间的所有结点的值的和。

注意哦,根节点的值大于左子树,并且根节点的值小于右子树

按深度优先搜索的顺序计算范围和。记当前子树根节点为root,分以下四种情况讨论:

1.root 节点为空,返回0;

2.root 节点的值大于high,由于二叉搜索树右子树上所有节点的值均大于根节点的值,即均大于 high,故无需考虑右子树,返回左子树的范围和;

3.root 节点的值小于low,由于二叉搜索树左子树上所有节点的值均小于根节点的值,即均小于 low,故无需考虑左子树,返回右子树的范围和;

4.root 节点的值在[low,high] 范围内,此时应返回root 节点的值、左子树的范围和、右子树的范围和这三者之和。

是不是感觉和递归别无二致,毕竟就是利用递归这个解题思想的嘛,那就来看看用纯递归思想该怎么解决本题。

OK,其实感觉还好吧,也不是很难,而且做多了会发现,什么递归呀,分治呀都很有趣,很妙。

例题2.岛屿数量 

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0,也就是说将1周围出现(上下左右)的1全部同化成0

例题3.背包问题

有n件物品,每件物品的重量为w[i], 价值为c[i]。现在需要选出若干件物品放入一个容量为v的背包中,使得在选入背包的物品重量和不超过容量v的前提下,让背包中物品的价格之和最大,求最大价值。

在这个问题中,需要从n件物品中选择若干件物品放入背包,使它们的价值之和最大。这样的话,对每件物品都有选和不选两种选择,而这就是所谓的“岔道口”。而当完成对于n件物品的选择之后就到达了“死胡同”,不过本题需要额外再多考虑一种情况,题目要求选择的物品总和不能超过v,因此一旦选择的物品重量总和超过v,也就会到达“死胡同”,所以这道题目需要多考虑一下,铁汁们要小心哦,具体的看代码实现。

三、思考题

今天的思考题部分是要求铁汁再把之前的有关递归的内容再看一下,多找些经典的递归题目练练手。加油哦

蓝桥杯算法竞赛系列第二章——深入理解重难点之递归(上)_安然无虞的博客-CSDN博客

四、蓝桥结语:遇见蓝桥遇见你,不负代码不负卿!

希望以上能帮助到铁汁们哦,也不枉俺挤时间去更新这块内容,如果感觉有所收获,可不可以给俺个三连加关注呢,让这篇文章可以被更多铁汁们看见,嘿嘿,拜托各位啦,笔者会再接再厉滴,886,咱们下次再见。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK