【递归、搜索与回溯】穷举vs暴搜vs深搜vs回溯vs剪枝

穷举vs暴搜vs深搜vs回溯vs剪枝

  • 1.全排列
  • 2.子集

在这里插入图片描述

点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃

管他什么深搜、回溯还是剪枝,画出决策树就完事了~~~

1.全排列

题目链接:46. 全排列

题目描述:

在这里插入图片描述

算法原理:

其实这道题本身是一个穷举(枚举)的题,3个数你可以三层for循环,但是如果10个数,100个数呢?对于数多的显然不合适!此时我们就需要借助递归把所有情况都枚举出来。

解决回溯的步骤:

  1. 画出决策树,越详细越好!
  2. 设计代码
    全局变量
    dfs函数
    细节问题

1.画出决策树

就是在暴力枚举这道题过程中如何不重不漏的把所有情况枚举到,就是把自己的想想法按照树的样子画下来。

第一次选择可以直接选123中任何一个,接下来每次选择都是从123中选。但是此时你会发现比如第一次选1,下一次还选1就会有重复的情况,因此这个1我们是要把它剪掉的,不考虑有1的情况。第二次选择假设选择的是2,在往下选择就还是有123,但是此时剪掉的应该更多,12不能选,只能选3,所有最终这条分支就是123,同理其他也是这样选出。
在这里插入图片描述

决策树画的越详细越好,就是把每一步都画出来,这样你就会发现每一个节点干的事情都是一样的,都是枚举整个数组。无非就是一些分支被我们剪掉。当一直在干同一件事情的时候我们决策树就画成功了,因为可以改成递归的代码。

2.设计代码

1.全局变量
全局变量就看这个递归需要什么东西和以及最终要返回什么东西。

全局变量的使用仁者见仁智者见智,也可以把全局变量换成参数在递归函数中传递。看个人选择,不过还是建议使用全局变量

首先来递归要返回的二维数组,再来一个把每次选择都要记录的path。
当path.size() == nums.size() 说明已经找到一个符合的组合,此时把path放到ret里,然后回溯 ,注意要 恢复现场。在说这个之前我们要说一说这个剪枝的事情。

在这里插入图片描述

剪枝怎么解决?就是如果避免下一次选择重复的数字。
此时我们也需要一个数组,弄一个bool 类型的数组。来判断一下该条路径下的数是否已经被使用过了。bool数组中记录nums数组中的数字是否已经被使用过。
check[0] 对应 1, check[1] 对应 2 , check[2] 对应3,check初始化都为false,只有对应数字被使用了 check[i]=true,说明这个数字已经被使用过了,下一次就不要选这个数字了。

在这里插入图片描述

2.dfs函数
仅需关心,某一个节点在干什么事情。

3.细节问题
回溯
当我要回去的时候,需要把这个3干掉,就是向上回去把path最后一个元素干掉,但是别忘记了check也是一个全局的, 你用3的时候会把check对应位置置为true,那你向上返回这个3你都不用了,是不是要把3复原啊。

  1. 干掉 path 最后一个元素
  2. 修改 check 数组
    在这里插入图片描述

剪枝
这里前面说过。

递归出口
遇到叶子节点的时候,直接添加结果。不用在到空了。

这样的题一定是决策树画出来是最重要的,第二步设计代码你想到哪一点写那一点都可以。

class Solution {
    vector<vector<int>> ret;
    vector<int> path;
    bool check[7]={false};
public:
    vector<vector<int>> permute(vector<int>& nums) {

        dfs(nums);
        return ret;
    }

    void dfs(vector<int>& nums)
    {
        if(path.size() == nums.size())
        {
            ret.push_back(path);
            //返回之前可以 回溯 --> 恢复现场
            return;
        }
        
        for(int i=0;i<nums.size();++i)
        {
            //剪枝
            if(check[i] == false)
            {
                path.push_back(nums[i]);
                check[i]=true;
                dfs(nums);
                //返回之后也可以 回溯 ---> 恢复现场  ,建立返回之后
                check[i]=false;
                path.pop_back();      
            }

        }
    }
};

注意一定是决策树越详细后面代码设计越轻松,代码设计考虑一下全局变量、dfs函数、细节问题。

2.子集

题目链接:78. 子集

题目描述:

在这里插入图片描述

算法原理:

这里我们采用两种策略来解决这个问题,虽然是两种策略,但都是深搜,所以我们的思考方式是一样的。

解法一:

  1. 画出决策树
  2. 设计代码
    全局变量
    dfs
    细节:回溯、剪枝、递归出口

首先画决策树,我们想想如何能够把所有子集不重不漏全部枚举出来。我们从子集定义出发,子集是这个数组里面选or不选某些数形成新的集合就是它的子集。因此我们就单独盯着数组中每个数字考虑选or不选来画出我们的决策树。
在这里插入图片描述
此时所有叶子节点就是数组所有不重复的子集。

现在我们仅需把这颗决策树转换成代码就可以了。之前的题无非就是直接把树画好给我们了,现在是要我们自己画一颗树。

既然叶子节点是我们的结果,因此我们需要两个全局变量,一个二维数组ret,一个一维数组path,path把每次选or不选路径记录下来,然后ret把path记录下来。这道题我们并不需要剪枝。

dfs函数我们就盯着某一个位置在干什么。比如绿圈的位置,因为上面已经选过了,所以要需要考虑这一次的数选or不选,所以dfs不仅要这个nums,还要告诉我你当前选到了那个位置,dfs(nums,pos)
就加入到path里,然后dfs(nums,pos+1)下一层
不选直接 dfs(nums,pos+1)下一层
在这里插入图片描述
细节问题:回溯要恢复现场,因此我们在选的这条路径在递归返回之后把path最后一个位置pop掉,剪枝我们这里没有。递归出口当pos==nums.size()的时候,把path放到ret里,然后返回即可。

class Solution {
    vector<vector<int>> ret;
    vector<int> path;
public:
    vector<vector<int>> subsets(vector<int>& nums) {

        dfs(nums,0);
        return ret;

    }

    void dfs(vector<int>& nums,int pos)
    {
        if(pos == nums.size())
        {
            ret.push_back(path);
            return;
        }
        
        // 选
        path.push_back(nums[pos]);
        dfs(nums,pos+1);
        path.pop_back(); // 恢复现场

        // 不选
        dfs(nums,pos+1);
    }
};

解法二:
也是上面一样的步骤,

  1. 画出决策树
  2. 设计代码
    全局变量
    dfs
    细节:回溯、剪枝、递归出口

这里我们换一种思考方式,当我们选的子集是没有元素、只有一个元素、只有两个元素、只有三个元素等等。前面的是盯着某一个数选or不选,现在我们直接看看最终要的子集要么没有,要么一个元素,要么两个元素,要么三个元素等等,从小到大去选。并且每次是从这个被选的数的后面再次选。并且每一个节点都是我们想要的结果。

在这里插入图片描述
你会发现我们这种解法比上面少了很多没有必要的情况。

全局变量还是上面那两个,dfs函数 从当前节点位置开始向后枚举,所以也要知道当前位置 dfs(nums,pos)。for(int i=pos;i<nums.size();++i) 把路径上的节点放入path,然后递归到下一层dfs(nums,pos+1),当递归返回收把path最后一个位置pop掉。 回溯也要恢复现场,把path最后一个位置pop掉,这里我们不用剪枝递归出口,每次进入递归函数后先把path先放到ret里。然后也不需要出口,循环条件不满足就出去了。

class Solution {
    vector<vector<int>> ret;
    vector<int> path;
public:
    vector<vector<int>> subsets(vector<int>& nums) {

        dfs(nums,0);
        return ret;

    }

    void dfs(vector<int>& nums,int pos)
    {
        ret.push_back(path);
        for(int i=pos;i<nums.size();++i)
        {
            path.push_back(nums[i]);
            dfs(nums,i+1);
            path.pop_back(); // 恢复现场
        }
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

部署LVS-DR模式(附带详细实验)

目录 一.数据包流向分析 二.DR模式特点 三.ARP问题及解决办法 四.实验部署 1.配置负载调度器&#xff08;192.168.80.105&#xff09; 1.1.安装并启用ipvsadm 1.2.配置虚拟IP地址&#xff08;VIP&#xff1a;192.168.80.100&#xff09; 1.3.调整 proc 响应参数 1.4.配…

【C#】pdf按页分割文件,以及分页合并,效果还不错,你值得拥有

欢迎来到《小5讲堂》 这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 背景效果单页分割文件合并多页分割插件说明相关文章 背景 最近遇到一…

MySQL视图教程(02):重命名视图

MySQL 重命名视图 在 MySQL 中&#xff0c; ALTER VIEW 语句用于重命名一个数据库视图&#xff08;View&#xff09;。 MySQL 是一种常用的关系型数据库管理系统&#xff0c;提供了丰富的功能和操作来管理数据库中的数据和对象。其中&#xff0c;重命名视图是 MySQL 中的一种…

Kettle根据分类实现Excel文件拆分

将整理好的一份供应商付款明细Excel文件&#xff0c;按供应商拆分成多个Excel文件。 实现思路 本文我们首先将供应商付款明细表&#xff0c;按照“名称”拆分成多份Excel文件。拆分Excel文件打算用两个转换实现&#xff0c;一个用来将Excel数据读取到参数中&#xff0c;另外一…

HBuilder X运行项目到微信开发者工具调试和发布Uniapp小程序

1.下载和安装 HBuilderX hbuilder首页&#xff1a;https://www.dcloud.io/hbuilderx.html 下载hbuilder编辑器,选择对应的系统,Windows和mac正式版即可,下载后免安装直接点击即可使用。 打开HBuilder之后&#xff0c;它会要求你注册一个用户&#xff0c;然后才可以使用。 …

25岁学plc还来的急嘛?

当然来得及&#xff01;25岁学习 PLC&#xff08;可编程逻辑控制器&#xff09;是完全可以的。我这里有一套plc入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习plc&#xff0c;不妨点个关注&#xff0c;给个评论222&#xff0c;私信22&…

PyTorch 维度变换-Tensor基本操作

以如下 tensor a 为例&#xff0c;展示常用的维度变换操作 >>> a torch.rand(4,3,28,28) >>> a.shape torch.Size([4, 3, 28, 28])view / reshape 两者功能完全相同: a.view(shape) >>> a.view(4,3,28*28) ## a.view(4,3,28,28) 可恢复squeeze…

红黑树的基本原理

目录 一.概念与性质 二.基本操作 1.建树 2.插入 情况一 情况二 3.查找 4.验证 三.红黑树与AVL树的比较 一.概念与性质 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根…

Java(蓝桥杯)一维二维数组应用

介绍&#xff1a; 一维数组&#xff0c;用来熟悉代码&#xff0c;主要考察二维数组&#xff1a; 二维数组存储行、列数据&#xff0c;遍历&#xff0c;输出结果 二维数组的旋转 二维数组数据的找规律。等等 二维数组问题&#xff0c;不难&#xff0c;但是比较繁琐。需要细…

在Linux中进行Redis的yum安装与配置

redis安装在IP为x.x.x.x的服务器上 redis是使用内存作为主存&#xff0c;使用硬盘来实现数据持久化&#xff0c;而且redis是周期性的将数据写到硬盘上。这就意味着一旦服务器出现断电、重启之类的情况&#xff0c;很可能会出现数据丢失的情况&#xff0c;因此不建议使用redis来…

全能型施耐德可编程控制器M241介绍

施耐德M241是一款通信强大、定位控制、丰富扩展于一身的全能型可编程控制器&#xff0c;适用于具有速度控制和位置控制功能的高性能一体型设备。其内置以太网通信端口&#xff0c;可以提供FTP和网络服务器功能&#xff0c;能够更为便捷地整合到控制系统架构中&#xff0c;通过智…

vue -ant -design 卡片是布局 实现动态计算 当前的 左右间距 实现居中

是这样的一个样式 我们使用display :flex 布局的时候 我们全部剧中 display: flex;align-items: center;justify-content: center; 如果是上述的代码来说的话 总是最后的一个也是会居中的 这样就比较丑 我们好像就没有什么好的办法了 我们这自己写的 肯定没有组件牛 如果有…

JVM 类加载器的工作原理

JVM 类加载器的工作原理 Java 虚拟机&#xff08;JVM&#xff09;的类加载器是 JVM 体系结构中的一个重要组件&#xff0c;它负责动态加载 Java 类到内存中。类加载器的工作原理涉及几个关键步骤和概念。本文将详细介绍 JVM 类加载器的工作原理。 1. 类加载器的概念 类加载器…

4-1RT-Thread信号量

4-1RT-Thread信号量 在实时系统中&#xff0c;一项工作往往需要多个线程共同完成。而线程对CPU的使用权由其优先级来确定。如果线程的功能是独立的&#xff0c;如控制LED灯周期性闪烁&#xff0c;那么我们只需要关注线程具体功能的实现即可。但在线程之间需要配合完成某些功能时…

家用路由器究竟有多费电?小白实测

小白最近听到了个笑话&#xff1a; 有个奶奶跟朋友说家里上不了网&#xff0c;让他去看看。朋友过去之后看到路由器被拔掉了&#xff0c;就问奶奶&#xff1a;“怎么把路由器拔掉了呀&#xff1f;”奶奶说&#xff1a;“那个东西的灯一闪一闪的&#xff0c;太费电&#xff0c;…

达内Angular学习

课程地址:1.1-环境搭建~1_哔哩哔哩_bilibili 一、环境搭建 安装前,确保node.js和npm包已经安装,并符合版本要求。 C:\Users\liutong>node -v v20.10.0C:\Users\liutong>npm -v 10.2.3 正式安装前,检查镜像是否为境内的镜像: C:\Users\liutong>npm config get…

Java MyBatis实战:QueryWrapper中的and和or拼接技巧

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、引言 在Java Web开发中&#xff0c;MyBatis是一个非常流行的持久层框架。它通过XML或注解的方式将Java对象与数据库表进行映射&#xff0c;从而实现数据的增删改查操作。在使用MyBatis的过程中&#xff0c;经常…

学习了解 JSON Schema

在数字时代&#xff0c;数据的快速增长要求开发者掌握有效的管理和验证技术。JSON&#xff08;JavaScript Object Notation&#xff09; 是一种流行的轻量级数据交换格式&#xff0c;在网络编程中有广泛应用。为了应对复杂数据的挑战&#xff0c;JSON Schema 诞生&#xff0c;提…

爱普生SMD3225贴片晶振升级版TSX-3225

爱普生有一款外形尺寸3.2*2.5mm的无源贴片晶振&#xff0c;型号TSX-3225&#xff0c;也是非常直观的能从型号分辨其封装尺寸大小的&#xff0c;被广泛应用于便携式的无线传输设备&#xff0c;同时&#xff0c;这也是一款非常成熟的产品&#xff0c;毕竟SMD3225封装是目前市场主…

功能强大的文本编辑器(绿色版)

UltraEdit 是一套功能强大的文本编辑器&#xff0c;可以编辑文本、十六进制、ASCII 码&#xff0c;完全可以取代记事本。 现在为你分享一个绿色免安装版&#xff0c;请在文末查看该软件的领取方法。 UltraEdit的强大功能 UltraEdit是一款功能强大的文本编辑器&#xff0c;广…