基础算法(五):DFS、BFS与剪枝

news2024/12/24 8:17:35

前言

        前面的基础算法笔记已经断更好久了,因为荔枝觉得还是得先学一下基础的数据结构知识之后才能更好的入门算法。在这篇文章中荔枝主要记录DFS和BFS的相关基础知识、答题的模板以及自己的一些浅薄理解,同样的也会涉及到相关的剪枝操作。

一、搜索算法概念

        在大多数实际的问题求解过程中,其实很多的问题都会被转化为搜索问题,不论是最优路径的选取还是极值的获取其实都是在搜寻着最符合我们预期的答案。换一种理解的方式:其实搜索算法也是通过有目的的枚举来获取最优解的过程。搜索算法又可以分为盲目搜索和启发式搜索,我们首先来看看盲目搜索的两种方式:深度优先搜索和广度优先搜索。


二、 深度优先搜索(DFS)

        深度优先搜索就是按照树的深度沿着树的某一分支遍历树的结点直到抵达目的结点的过程,如果该分支无法抵达目的节点则会进行回溯到前面的结点并再次进行深度优先搜索直至找到目的结点。DFS一般使用递归的方式和栈这一数据结构,因此DFS如果不进行剪枝操作效率会比较低。在数据结构中,二叉树是一种使用DFS场景比较多的数据结构,换而言之,图论中的深度优先搜索其实就是二叉树的递归遍历过程,像二叉树最长路径、二叉树遍历、二叉树深度等等经典的题目往往都需要使用到深度优先搜索的方法。但也正如前面所说的那样,其实二叉树深度优先搜索算法的底层实现还是栈这一数据结构,这就要求我们在前面的使用的时候需要考虑到我们的程序执行会不会爆栈,这时候也就需要一定的操作或者技巧来简化深度优先搜索的过程。

        荔枝在刷完leecode的DFS简单题目后直观感觉其实DFS用得好最主要的就是你的递归算法的基本功牢不牢,其实DFS也就是一种递归的体现,归结为其实任何算法题目都是“有迹可循”的,我们可以看看这几道题目来大致弄清楚深度优先搜索的打题模板,之后再面对DFS的题目能有一个大致的方向。

2.1 Leecode104——二叉树的最大深度

题目描述
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
输入示例
[3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
输出示例:
3

        这道题目其实就是求二叉树一共有多少层,这个场景可以说其实就非常适合使用DFS来求解了,具体思路也很简单:从根节点沿着所有的路径走一遍并根据路径中的节点组成的层数来更新并返回最大深度。可以看到荔枝给了两种代码,很明显最下面的官方题解接很简洁明了哈哈哈,大家可以看出其实最基本的就是递归的运用。

demo示例:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int maxDepth(TreeNode* root) {
    //最大深度其实也是一种最长的路径
    //首先先确实边界条件
        if(root == nullptr){
            return 0;
        }
        if(root->left==nullptr && root->right==nullptr){
            return 1;
        }
        int depth = 0;
        if(root->left!=nullptr){
            depth=max(depth,maxDepth(root->left));
        }
        if(root->right!=nullptr){
            depth = max(depth,maxDepth(root->right));
        }
        return depth+1;
    }
};
// 另一种思路
// class Solution {
// public:
//     int maxDepth(TreeNode* root) {
//         if (root == nullptr) return 0;
//         return max(maxDepth(root->left), maxDepth(root->right)) + 1;
//     }
// };
来源:力扣(LeetCode)
链接: https://leetcode.cn/problems/maximum-depth-of-binary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

2.2 Leecode257——二叉树的所有路径

题目描述:
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。

输入示例:
[1,2,3,null,5]
输出:
["1->2->5","1->3"]

        这道题的解题思路跟上面的题目其实是一样的,对于一个节点,我们仅需要确定一条路径的终止记录的条件:就是遇到叶子节点,而对于一个根节点我们需要的是将左子树和右子树的路径记录下即可,我们无需太过纠结递归的过程实现,毕竟人脑压不了那么多栈哈哈哈哈哈。

demo示例:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void getPath(TreeNode* root,vector<string>& v,string s){
        if(root!=nullptr){
            s = s + "->" + to_string(root->val);
            if(root->left==nullptr && root->right==nullptr){
                v.push_back(s);
            }else{
                getPath(root->left,v,s);
                getPath(root->right,v,s);
            }
        }
        
    }
    
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> v;
        // 判断边界条件
        if(root == nullptr){
            return v;
        }else{
            if(root->left==nullptr && root->right==nullptr){
                v.push_back(to_string(root->val));
                return v;
            }
            getPath(root->left,v,to_string(root->val));
            getPath(root->right,v,to_string(root->val));
        }
        return v;
    }
   
};
来源:力扣(LeetCode)
链接: https://leetcode.cn/problems/binary-tree-paths/
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

2.3 Leecode783——二叉搜索树节点的最小距离

题目描述:
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。

输入示例:
[4,2,6,1,3]
输出示例:
1

demo示例:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int result=1000000;
    TreeNode* node = nullptr;
    int minDiffInBST(TreeNode* root) {
        dfs(root);
        return result; 
    }
    void dfs(TreeNode* root){
        if(root==nullptr) return;
        dfs(root->left);
        if(node!=nullptr){
            result=min(result,abs(root->val-node->val));
        }
        node = root;
        dfs(root->right);
    }
};
题目来源:力扣(LeetCode)
链接: https://leetcode.cn/problems/minimum-distance-between-bst-nodes/
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

        我们已经上面三道简单题目,其实可以看到递归的影子无处不在,这也是荔枝的感受。也就是说我们再拿到一道DFS问题的时候需要直接反应出来找递归实现的条件,并抓住递归这一个方向来解决问题。对于DFS题目来说,更多的例题还是在考察二叉树的递归序列的应用,通过对前面二叉树题目的答题模板有了大致的了解,但是可能大多人会跟荔枝一样,对于这些概念都很清楚,但做起题目来还是一看就会,一写就废,我们应该如何去解题呢?首先还是需要弄清楚题目的意思,这是最重要的;其次我们需要对二叉树的递归顺序和二叉树的递归遍历了解清楚,知道什么时候适合用前序遍历、什么时候适合用后序遍历、什么时候使用中序遍历。具体举个例子:比如在Leecode.236二叉树的最近公共祖先这道题目中,我们需要从叶端往根端去处理,这时候就需要用到中序遍历来实现。因此什么题目适合什么方向来解题取决于你对二叉树知识的灵活运用,这很重要!


三、广度优先搜索(BFS)

        广度优先搜索与深度优先搜索相比最大的不同就是BFS更加侧重于状态的选取和标记。也就是说,BFS在遇到每一搜索层的时候就会标记所有的岔路并注意进入岔路并标记,通过反复的标记和回退,将通往目的结点的路径(最优解)记录下来。BFS使用到的数据结构是队列,它的复杂度跟深度优先搜索相同,本质上都是一种穷举的方法。其实简单的借助二叉树来理解广度优先搜索更好,因为二叉树的层序遍历其实就类似于图论中的广度优先搜索。我们先来看看这张图:

        从上面的一个简单的二叉树中我们使用层序遍历遍历其节点:4-2-3-6-4-7-5,简单的说就是按层来遍历,类比于BFS来说我们是按层来搜索的,因此我们才称为广度优先搜索。那么如何使用广度优先搜索来解决题目,首先跟荔枝来看看这道例题:

3.1 Leecode102.——二叉树的层序遍历

题目描述:
给你二叉树的根节点 root ,返回其节点值的层序遍历 (即逐层地,从左到右访问所有节点)。
输入:
root = [3,9,20,null,null,15,7] 输出:
[[3],[9,20],[15,7]]

demo示例:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> v;
        queue<TreeNode*> q;
        if(root!=nullptr) q.push(root);
        while(!q.empty()){
            int size = q.size();
            vector<int> vec;
            while(size--){
                TreeNode* front = q.front();
                vec.push_back(front->val);
                q.pop();
                if(front->left!=nullptr){
                    q.push(front->left);
                }
                if(front->right!=nullptr){
                    q.push(front->right);
                }
            }
            v.push_back(vec);
        }
        return v;
    }
};

        这道题目就是一道非常经典的BFS题目了,首先我们借助了队列这一数据结构,可以看到这里用了两层while循环,外层的循环其实记录当前层级需要处理的节点数目,因为在开始对子节点进行处理的时候队列的大小会发生变化,所以需要提前记住当前层级的节点数目,内层循环则是对当前层级的二叉树节点进行分别进行出队操作和将其左右子节点压入队列中。需要注意的是我们需要一开始将根节点入队,才能顺利进入循环中,同时根节点入队操作的出发判别也作为了正确输入的边界条件。

来源:力扣(LeetCode)
链接: https://leetcode.cn/problems/binary-tree-level-order-traversal/
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

3.2 二叉树的最大深度问题

关于二叉树的最大深度问题我们已经在2.1中详细的介绍了,这道题目是Leecode的第104题,这里荔枝也就不再对问题的描述和输入输出的用例进行赘述了,我们现在尝试着使用BFS的模板来解题。

demo示例:

class Solution{
    public:
        int maxDepth(TreeNode* root){
            int depth = 0;
            queue<TreeNode*> q;
            if(root!=nullptr){
                q.push(root);
            }
            while(!q.empty()){
                int size = q.size();
                while(size--){
                    TreeNode* node = q.front();
                    q.pop();
                    if(node->left!=nullptr){
                        q.push(node->left);
                    }
                    if(node->right){
                        q.push(node->right);
                    }
                }
                depth++;
            }
            return depth;
        }
};

可以看出这里荔枝采用的是BFS的经典模板解法,在每层的队列循环结束之后都会让层数depth自增一,从而算出该二叉树的最大深度。

3.3 二叉树的最小深度

我们再来看看这道题目:

题目描述:
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。

输入示例:
root = [3,9,20,null,null,15,7]

输出示例:
2
来源:力扣(LeetCode)
链接: https://leetcode.cn/problems/minimum-depth-of-binary-tree/
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

demo示例:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int minDepth(TreeNode* root) {
        if(root==nullptr){
            return 0;
        }
        queue<TreeNode*> q;
        int depth = 0;
        q.push(root);
        while(!q.empty()){
            int size = q.size();
            depth++;
            while(size--){
                TreeNode* node = q.front();
                q.pop();
                if(node->left){
                    q.push(node->left);
                }
                if(node->right){
                    q.push(node->right);
                }
                if(node->left==nullptr && node->right==nullptr){
                    return depth;
                }
            }
            
        }
        return depth;
    }
};

四、剪枝操作

4.1 可行性剪枝

        可行性剪枝其实就是根据题目的要求对DFS或者是BFS所枚举出的所有搜索可能进行裁剪,如果对于其中的一种状态情况可以推出按照这个方向之后的所有的情况都与题目的要求不符,那么就对当前的所有情况和之后的所有情况进行判负,直接放回当前的状态结果。

4.2 最优性剪枝

        在求解最优化的问题时,往往可能应为暴力枚举或者是盲目搜索而导致程序超时,这时候我们需要借助于我们已有的最优解,对于搜索得到的结果比最优结果差,那我们就无需再往这个方向搜索,直接返回程序的状态即可。

4.3 重复性剪枝

重复性剪枝其实就是排除等效冗余的过程,对于可能出现的重复出现最优解的请况仅需选取一种即可。

4.4 奇偶性剪枝

奇偶性剪枝更多的应用在迷宫问题中,我们往往借助奇数和偶数的特性对路径结果搜索进行简化。我们来看看一个迷宫问题:

有一个n*m的迷宫,一个探险者每个单位时间只能上下左右移动一格,现在规定T时间目的地大门打开,问探险者能逃离迷宫吗?注意不能走回头路,也不能碰到障碍物。在解题时我们可以将将n * m的网格染成黑白色,相邻的两个格子的颜色不一样。我们记每个格子的行数和列数之和为x,如果x为偶数,那么格子就是白色,反之奇数时为黑色。我们可以得出这样一个结论:走奇数会改变颜色,走偶数步则颜色不变。如果起点和终点的颜色一样,而T是奇数的话,就不可能离开迷宫。

借助这个处理过程我们可以根据具体题目的要求将路径的搜索范围进行缩小。


总结

在这篇文章中荔枝主要介绍了有关盲目搜索算法的基本分类、相关基础知识以及相应的优化方法——剪枝操作,同时荔枝也给出了相应的经典题目和刷题模板,希望能帮到正在学习的小伙伴哈哈哈哈哈。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/479582.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java基础学习-7

文章目录 异常小总结编译时异常和运行时异常小总结 异常在代码中的两个作用异常的方式JVM虚拟机默认处理异常的方式自己处理&#xff08;捕获异常&#xff09;灵魂四问抛出异常异常中的常见方法小总结小练习 自定义异常 File----路径File的概述和构造方法小总结 File的成员方法…

线性表之顺序表(C语言实现)

前言 &#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f; c语言初阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:讲解数据结构的入门知识,线性结构之顺序表. 金句分享:…

机器学习笔记 基于深度学习的边缘检测

一、Holistically-Nested Edge Detection 边缘检测是视觉工作中十分常用的技术,传统边缘检测已经包含了很多经典的诸如Canny、Robert等等,都是各有擅场,不过有一点问题,就是很多参数需要人工调试,所以深度学习研究人员提出了基于卷积神经网络的边缘检测算法。 即HED,该算…

uniapp拍照离线定位,获取图片信息,经纬度解析地址

✅作者简介&#xff1a; 我是痴心阿文&#xff0c;你们的学友哥&#xff0c;今天给大家分享uniapp拍照离线定位&#xff0c;获取图片信息&#xff0c;经纬度解析地址 &#x1f4c3;个人主页:痴心阿文的博客_CSDN博客-Vue.js,数组方法,前端领域博主 &#x1f525;本文前言&#…

【数据结构】超详细之顺序表(利用C语言实现)

文章目录 前言一、顺序表是什么&#xff1f;二、顺序表的实现步骤 1.顺序表的初始化以及开辟空间2.实现顺序表的头插、尾插以及打印3.实现顺序表的头删、尾删以及打印4.实现顺序表的查找5.实现顺序表指定位置插入6.实现顺序表指定位置删除7.释放内存总结 前言 数据结构是一个程…

离散数学_九章:关系(3)

9.3 关系的表示 1、用集合表示关系2、用矩阵表示关系矩阵表示关系⭐集合上的关系矩阵 R 自反时 R 对称时 R 反对称时 ⭐确定关系合成的矩阵 3、用有向图表示关系有向图⭐从有向图中 确定关系具有的属性 自反性对称性反对称性传递性 本节及本章的剩余部分研究的所有关系均为二…

函数(详解)——C语言

Hello&#xff0c;友友们前段时间忙拖更了这么久&#xff0c;趁着五一假期好好卷上一波哈哈哈。好的&#xff0c;我们本期主要对C语言中的函数进行讲解。 1.什么是函数 在数学中也常常用到函数&#xff0c;但你对C语言中的函数有了解吗&#xff1f; 在C语言中函数是一段可重…

数据结构学习分享之单链表详解

数据结构第三课 1. 前言2. 链表的概念以及结构3. 链表的分类4.链表的实现4.1 初始化结构4.2 尾插函数4.3 尾删函数4.4 头插函数4.5 头删函数4.6 开辟新节点4.7 销毁链表 5. 单链表OJ题目6. 顺序表和链表的区别7. 总结 1. 前言 &#x1f493;博主CSDN:杭电码农-NEO&#x1f493;…

五年开发经验前端程序员,刚入职一个月就要离职,我来聊聊看法

最近有一个新来的同事&#xff0c;估计又要离职了吧。从他的工作经历来看&#xff0c;大概有5年的前端工作经验&#xff0c;但是头发看起来挺少的&#xff0c;不知道是工作加班导致的&#xff0c;看他的性格不太像是经常加班的。 他这个人就是我们公司人事面试的&#xff0c;虽…

操作系统——进程管理

0.关注博主有更多知识 操作系统入门知识合集 目录 0.关注博主有更多知识 4.1进程概念 4.1.1进程基本概念 思考题&#xff1a; 4.1.2进程状态 思考题&#xff1a; 4.1.3进程控制块PCB 4.2进程控制 思考题&#xff1a; 4.3线程 思考题&#xff1a; 4.4临界资源与临…

躺平减脂减重法补充篇——无需控制碳水摄入的有效方法,另推一种健康的运动和防止老年慢性病的方式...

本文此前已经连续发表了六篇相关文章&#xff0c;内容确实比较多&#xff0c;最近又做了一组实验&#xff0c;进食了大量的锅巴&#xff0c;看看是否会带来体重的增加&#xff0c;每天进食量都不少于200克锅巴&#xff0c;对&#xff0c;4两重&#xff0c;而且是在每天正常进食…

SAPUI5 之XML Views (视图) 笔记

文章目录 官网 Walkthrough学习-XML Views视图案例要求&#xff1a;我们把上面通过index.html body的展示放在XML中展示1.0.1 新增view文件夹1.0.2 在xml文件中新增一个Text 文本1.0.3 在index.js中实例化view视图1.0.4 执行刷新浏览器1.0.5 调试界面分析结果 官网 Walkthrough…

假期给朋友介绍如何学习java和找工作的建议?

Java学习 一、学习Java的建议1. 学习Java基础知识2. 学习Java框架3. 学习Java Web开发4. 学习Java数据库编程5. 学习Java工具6.学习Java中的多线程技术6. 练习编程 二、找工作的建议1. 准备好简历2. 寻找工作机会3. 准备面试4. 提高自己的技能5. 关注行业动态 学习Java和找工作…

第十九章 观察者模式

文章目录 前言普通方式解决问题CurrentConditions 显示当前天气情况WeatherData 管理第三方Clint 测试 一、观察者模式(Observer)原理完整代码SubjectObserverWeatherData implements SubjectCurrentConditions implements ObserverBaiduSite implements ObserverClint 前言 普…

《软件工程教程》(第2版) 主编:吴迪 马宏茹 丁万宁 第十章课后习题参考答案

第十章 面向对象设计 课后习题参考答案 一、单项选择题 &#xff08;1&#xff09;A &#xff08;2&#xff09;B &#xff08;3&#xff09;B &#xff08;4&#xff09;D &#xff08;5&#xff09;A &#xff08;6&#xff09;C&#xff08;7&#xff09;D &#xff0…

【学习心得】Python多版本控制

问题描述&#xff1a;本文主要解决Windows系统下的多个Python版本共存问题。 &#xff08;一&#xff09;安装不同版本Python 官方下载链接&#xff1a;Python Releases for Windows | Python.org 下载如图中所示的版本&#xff08;64位Windows系统可执行安装包版本&#xff0…

赞!数字中国建设峰会上的金仓风采

4月30日&#xff0c;第六届数字中国建设成果展览会圆满落幕。人大金仓深度参与本届峰会&#xff0c;在会上发布产品新版本&#xff0c;展出国产数据库前沿的行业解决方案和创新应用成果&#xff0c;出席国资央企SaaS应用服务共享平台伙伴签约仪式&#xff0c;吸引众多用户、伙伴…

面试官:你知道 Spring lazy-init 懒加载的原理吗?

普通的bean的初始化是在容器启动初始化阶段执行的&#xff0c;而被lazy-init修饰的bean 则是在从容器里第一次进行context.getBean(“”)时进行触发。 Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap里供下面的初始…

k210单片机定时器的应用

定时器应该是一个单片机的标准配置&#xff0c;所以k210也是有的&#xff0c;拥有3个定时器&#xff0c;具体的使用方法我们往下看&#xff1a; 分步介绍&#xff1a; 首先是相关模块的使用 构造函数&#xff1a; machine.Timer(id,channel,modeTimer.MODE_ONE_SHOT,period100…

【7. ROS 中的 IMU 惯性测量单元消息包】

欢迎大家阅读2345VOR的博客【6. 激光雷达接入ROS】&#x1f973;&#x1f973;&#x1f973; 2345VOR鹏鹏主页&#xff1a; 已获得CSDN《嵌入式领域优质创作者》称号&#x1f47b;&#x1f47b;&#x1f47b;&#xff0c;座右铭&#xff1a;脚踏实地&#xff0c;仰望星空&#…