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

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

  1. 穷举 :最基础的思想
    • 定义:从所有可能的解中,逐一检查每个候选解是否满足条件,直到找到答案(或确认无解)。
    • 特点:不依赖任何策略,纯粹 "地毯式" 覆盖所有可能性,是最朴素的搜索思想。
  2. 暴搜 :穷举的 "实现方式"
    • 定义:"暴力搜索" 的简称,是穷举思想的具体实现,通常指无优化地遍历所有可能解(本质上是 "未优化的穷举")。
    • 与穷举的关系:穷举是思想,暴搜是这种思想的直接落地方式,两者常被混用,但暴搜更强调 "不做任何优化,硬刚所有可能"。
  3. 深搜 :暴搜的 "遍历策略"
    • 定义:深度优先搜索,是暴搜的一种具体遍历方式 ------ 优先沿着一条路径 "走到底",直到无法继续再回溯,换另一条路径探索。
    • 与暴搜的关系:深搜是暴搜的 "优化形式" 之一,它通过明确的遍历顺序避免重复探索同一路径,比 "无规律的暴搜" 更有序。
  4. 回溯 :深搜的 "改进版"
    • 定义:在深搜的基础上,增加 "撤销选择" 的操作 ------ 当探索到某一步发现当前路径不可能通向解时,不仅退回上一步,还会 "清理当前步骤的影响",以便重新尝试其他路径。
    • 与深搜的关系:回溯是 "带状态重置的深搜",是深搜的精细化实现,解决了深搜中 "状态污染" 的问题。
  5. 剪枝 :回溯 / 深搜的 "优化手段"
    • 定义:在回溯或深搜过程中,通过提前判断 "当前路径不可能通向解",直接终止该路径的探索,避免无效分支消耗资源。
    • 与回溯 / 深搜的关系:剪枝是对回溯或深搜的 "效率优化",不改变其核心逻辑,只减少不必要的计算。

一、全排列

题目描述:

思路讲解:

本道题需要我们从给定一个不含重复数字的数组 nums ,返回其所有可能的全排列。通过递归尝试所有可能的元素排列,在每一步选择一个未使用的元素加入当前排列,完成选择后回溯并尝试下一个元素,直到生成所有完整排列。以下是具体的思路:

  • 核心逻辑
    • 全排列的本质是从数组中依次选择元素,每个元素在排列中只能出现一次。通过递归逐层确定排列的每个位置,使用 "选择 - 递归 - 回溯" 的流程遍历所有可能的组合
  • 全局变量
    • path:当前正在构建的排列
    • isuse:记录元素是否已被使用的布尔数组(避免重复选择)
    • ans:存储所有完整排列的结果集(引用传递)
  • 终止条件
    • 当 path 的长度等于 nums 的长度时,说明已生成一个完整排列,将其加入 ans 并返回
  • 递归逻辑
    • 遍历数组中的每个元素,若元素未被使用(isuse[i] == false):
      • 将元素加入 path,标记 isuse[i] = true
      • 继续递归构建下一个位置的元素
      • 递归返回后,将元素从 path 中移除,标记 isuse[i] = false,以便其他分支使用该元素

编写代码:

cpp 复制代码
class Solution {
    vector<vector<int>> ans;
    bool isuse[7];
    vector<int> path;
    int len;
public:
    void _permute(vector<int>& nums) 
    {
        if(path.size() == len)
        {
            ans.push_back(path);
            return;
        }

        for(int i = 0 ; i < len ; i++)
        {
            if(isuse[i] == false)
            {
                path.push_back(nums[i]);
                isuse[i] = true;
                _permute(nums);
                path.pop_back();
                isuse[i] = false;
            }
        }
    }

    vector<vector<int>> permute(vector<int>& nums) {
        len = nums.size();
        memset(isuse,false,sizeof(isuse));
        _permute(nums);

        return ans;
    }
};

二、子集

题目描述:

思路讲解:

本道题需要我们根据整数数组 nums ,返回该数组所有可能的子集。逐步选择元素,构建所有可能的子集,通过回溯法在每一步决定是否选择当前元素,最终生成所有不重复的子集。

  • 核心逻辑
    • 子集问题的本质是从数组中选择任意个元素(包括 0 个),形成不重复的集合。递归过程中,对于每个元素,有两种选择:加入当前子集或不加入当前子集,通过遍历所有选择组合生成完整解集。
  • 全局变量
    • path:当前正在构建的子集
    • ans:存储所有子集的结果集
  • 终止条件
    • 当遍历完数组所有元素(pos == nums.size())时,将当前子集 path 加入 ans 并返回。
  • 递归逻辑
    • 不选择当前元素:直接递归处理下一个元素,当前子集不变。
    • 选择当前元素:将 nums[pos] 加入 path,递归处理下一个元素,之后回溯(从 path 中移除该元素)。

编写代码:

cpp 复制代码
class Solution {
    vector<vector<int>> ans;
    vector<int> path;
    int len;
public:
    void _subsets(vector<int>& nums , int pos) {
        if(pos == len)
        {
            ans.push_back(path);
            return;
        }

        path.push_back(nums[pos]);
        _subsets(nums,pos+1);
        path.pop_back();

        _subsets(nums,pos+1);
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        len = nums.size();
        _subsets(nums,0);

        return ans;
    }
};

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。

希望大家以后也能和我一起进步!!🌹🌹

如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

相关推荐
l1t2 分钟前
利用美团longcat.ai编写的C语言支持指定压缩算法通用ZIP压缩程序
c语言·开发语言·人工智能·算法·zip·压缩
hansang_IR37 分钟前
【线性代数基础 | 那忘算9】基尔霍夫(拉普拉斯)矩阵 & 矩阵—树定理证明 [详细推导]
c++·笔记·线性代数·算法·矩阵·矩阵树定理·基尔霍夫矩阵
lingchen190644 分钟前
MATLAB矩阵及其运算(三)矩阵的创建
算法·matlab·矩阵
野犬寒鸦1 小时前
力扣hot100:矩阵置零(73)(原地算法)
java·数据结构·后端·算法
CoovallyAIHub3 小时前
4亿数据训练,零样本能力惊人:CLIP模型全解读
深度学习·算法·计算机视觉
Rotch4 小时前
核密度估计(KDE)(二)
算法
only-qi5 小时前
leetcode110. 平衡二叉树
数据结构·算法
要做朋鱼燕5 小时前
【C++】Vector核心实现:类设计到迭代器陷阱
开发语言·c++·笔记·算法·职场和发展
Go_Zezhou5 小时前
在线性代数里聊聊word embedding
线性代数·算法·机器学习·nlp
Christo36 小时前
TFS-2005《A Possibilistic Fuzzy c-Means Clustering Algorithm》
人工智能·算法·机器学习