当前位置: 首页 > news >正文

递归、搜索与回溯算法 —— 名词解析

目录

一、递归

1、什么是递归?

2、递归的数学类比

3、为什么要用到递归?

问题具有递归结构:

代码简洁易懂:

解决复杂问题:

处理嵌套结构:

4、如何理解递归?

明确基准条件:

将问题分解为子问题:

信任递归:

5、递归的示例:计算阶乘

6、如何写好递归?

1. 明确基准条件

2. 确保问题规模逐步减小

3. 避免重复计算

4. 控制递归深度

5. 清晰定义递归函数的输入和输出

6. 测试边界条件

7、递归的经典示例

示例 1:斐波那契数列

示例 2:二叉树的前序遍历

8、递归与迭代的比较

9、总结

二、搜索&深度优先遍历遍历&深度优先探索&宽度优先遍历&宽度优先探索&暴搜 

1. 搜索(Search)

含义

作用

2. 深度优先搜索(DFS)

含义

特点

代码示例(图的 DFS)

应用场景

3. 广度优先搜索(BFS)

含义

特点

代码示例(图的 BFS)

应用场景

4. 深度优先探索(Depth-First Exploration)

含义

特点

应用场景

5. 广度优先探索(Breadth-First Exploration)

含义

特点

应用场景

6. 暴力搜索(Brute Force)

含义

特点

应用场景

7. 关系图

8. 扩展搜索问题

状态空间搜索

组合优化问题

图论问题

游戏树搜索

9. 总结

三、 回溯与剪枝

1. 回溯(Backtracking)

本质

特点

回溯的步骤

代码示例(全排列问题)

应用场景

2. 剪枝(Pruning)

本质

特点

剪枝的实现方法

代码示例(组合总和问题)

应用场景

3. 回溯与剪枝的关系

关系图

4. 扩展搜索问题

数独问题

八皇后问题

组合优化问题

5. 总结


一、递归

1、什么是递归?

        递归(Recursion)是一种在函数定义中调用自身的技术。在计算机科学中,递归是一种通过将问题分解为更小的、相似的子问题来解决问题的方法。递归函数通常包含两个部分:

  1. 基准条件(Base Case):递归终止的条件。当满足基准条件时,递归不再继续,直接返回结果。

  2. 递归条件(Recursive Case):将问题分解为更小的子问题,并调用自身来解决这些子问题。

2、递归的数学类比

递归的思想类似于数学中的递推关系。例如,斐波那契数列的定义:

  • 基准条件:F(0)=0F(0)=0,F(1)=1F(1)=1。

  • 递归条件:F(n)=F(n−1)+F(n−2)F(n)=F(n−1)+F(n−2)。

在编程中,递归的实现方式与数学中的递推关系非常相似。


3、为什么要用到递归?

递归是一种强大的工具,适用于以下场景:

  1. 问题具有递归结构

    • 如果一个问题可以自然地分解为更小的、相似的子问题,递归是解决它的直观方法。

    • 例如,树的遍历、分治算法(如归并排序、快速排序)等。

  2. 代码简洁易懂

    • 递归代码通常比迭代代码更简洁,更接近问题的数学定义。

    • 例如,计算阶乘、斐波那契数列等问题,递归代码非常直观。

  3. 解决复杂问题

    • 递归可以简化复杂问题的解决过程,例如回溯算法、动态规划等。

  4. 处理嵌套结构

    • 递归非常适合处理嵌套结构,例如树、图、链表等。


4、如何理解递归?

理解递归的关键在于:

  1. 明确基准条件

    • 递归必须有一个明确的终止条件,否则会导致无限递归,最终栈溢出。

  2. 将问题分解为子问题

    • 每次递归调用都应该使问题规模减小,逐步逼近基准条件。

  3. 信任递归

    • 假设递归函数已经能够正确解决子问题,专注于如何利用子问题的结果解决当前问题。

5、递归的示例:计算阶乘

int factorial(int n) {
    // 基准条件
    if (n == 0) return 1;
    // 递归条件
    return n * factorial(n - 1);
}
  • 基准条件:n=0n=0 时,返回 11。

  • 递归条件:n>0n>0 时,返回 n×factorial(n−1)n×factorial(n−1)。


6、如何写好递归?

写好递归代码需要遵循以下原则:

1. 明确基准条件

  • 基准条件是递归的终止条件,必须清晰明确。

  • 例如,在计算阶乘时,基准条件是 n=0n=0。

2. 确保问题规模逐步减小

  • 每次递归调用都应该使问题规模减小,否则会导致无限递归。

  • 例如,在计算阶乘时,每次递归调用 nn 都会减小。

3. 避免重复计算

  • 如果递归过程中存在重复计算,可以使用记忆化(Memoization)或动态规划来优化。

  • 例如,斐波那契数列的递归实现可以通过记忆化避免重复计算。

4. 控制递归深度

  • 递归深度过大会导致栈溢出。可以通过尾递归优化或转换为迭代来减少递归深度。

5. 清晰定义递归函数的输入和输出

  • 递归函数的输入参数和返回值应该清晰明确,便于理解和使用。

6. 测试边界条件

  • 递归代码容易在边界条件下出错,因此需要仔细测试基准条件和边界情况。


7、递归的经典示例

示例 1:斐波那契数列

int fibonacci(int n) {
    // 基准条件
    if (n == 0) return 0;
    if (n == 1) return 1;
    // 递归条件
    return fibonacci(n - 1) + fibonacci(n - 2);
}
  • 问题:存在大量重复计算,效率低。

  • 优化:使用记忆化或动态规划。

示例 2:二叉树的前序遍历

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

void preorder(TreeNode* root) {
    if (root == nullptr) return; // 基准条件
    cout << root->val << " ";    // 访问当前节点
    preorder(root->left);        // 递归遍历左子树
    preorder(root->right);       // 递归遍历右子树
}
  • 基准条件:当前节点为空时返回。

  • 递归条件:访问当前节点,然后递归遍历左子树和右子树。


8、递归与迭代的比较

特性递归迭代
代码简洁性更简洁,接近数学定义通常更冗长
可读性更易理解可能需要更多注释
性能可能有栈溢出风险,效率较低通常效率更高
适用场景树、图、分治、回溯等问题线性问题、需要控制栈空间的问题

9、总结

  • 递归是一种通过将问题分解为更小的子问题来解决问题的方法。

  • 使用递归的场景包括问题具有递归结构、代码简洁性要求高、处理嵌套结构等。

  • 理解递归的关键是明确基准条件、将问题分解为子问题,并信任递归。

  • 写好递归需要明确基准条件、确保问题规模逐步减小、避免重复计算、控制递归深度等。

  • 递归和迭代各有优缺点,应根据具体问题选择合适的方法。

二、搜索&深度优先遍历遍历&深度优先探索&宽度优先遍历&宽度优先探索&暴搜 

1. 搜索(Search)

含义

搜索是指在给定的问题空间中,通过某种策略寻找目标解的过程。搜索算法可以分为:

  • 系统性搜索:按照一定的规则遍历问题空间(如 DFS、BFS)。

  • 启发式搜索:利用启发信息指导搜索方向(如 A* 算法)。

作用

  • 解决组合优化问题(如路径规划、状态空间搜索)。

  • 遍历数据结构(如树、图的遍历)。

  • 寻找满足条件的解(如数独、八皇后问题)。


2. 深度优先搜索(DFS)

含义

        深度优先搜索是一种系统性搜索算法,其核心思想是尽可能深地探索某一条路径,直到无法继续为止,然后回溯并尝试其他路径

特点

  • 使用(递归或显式栈)实现。

  • 适合解决连通性问题回溯问题

  • 可能陷入无限循环(如果问题空间无限且未剪枝)。

代码示例(图的 DFS)

void dfs(int node, vector<vector<int>>& graph, vector<bool>& visited) {
    visited[node] = true; // 标记当前节点为已访问
    for (int neighbor : graph[node]) {
        if (!visited[neighbor]) {
            dfs(neighbor, graph, visited); // 递归访问邻居节点
        }
    }
}

应用场景

  • 图的连通性检测。

  • 拓扑排序。

  • 回溯问题(如八皇后、数独)。


3. 广度优先搜索(BFS)

含义

广度优先搜索是一种系统性搜索算法,其核心思想是从起点开始,逐层扩展,直到找到目标解

特点

  • 使用队列实现。

  • 适合解决最短路径问题(在无权图中)。

  • 不会陷入无限循环(如果问题空间有限)。

代码示例(图的 BFS)

void bfs(int start, vector<vector<int>>& graph) {
    queue<int> q;
    vector<bool> visited(graph.size(), false);
    q.push(start);
    visited[start] = true;
    while (!q.empty()) {
        int node = q.front();
        q.pop();
        for (int neighbor : graph[node]) {
            if (!visited[neighbor]) {
                visited[neighbor] = true;
                q.push(neighbor);
            }
        }
    }
}

应用场景

  • 无权图的最短路径问题。

  • 状态空间搜索(如迷宫问题)。

  • 社交网络中的最短关系链。


4. 深度优先探索(Depth-First Exploration)

含义

        深度优先探索是深度优先搜索的一种扩展,通常用于探索未知的问题空间。它不仅限于图或树的遍历,还可以用于解决更复杂的问题(如状态空间搜索)。

特点

  • 强调探索而非简单的遍历。

  • 通常结合剪枝回溯技术。

应用场景

  • 解决组合优化问题(如旅行商问题)。

  • 游戏树搜索(如围棋、象棋)。


5. 广度优先探索(Breadth-First Exploration)

含义

        广度优先探索是广度优先搜索的一种扩展,通常用于在未知问题空间中寻找最优解。它逐层扩展,确保找到的解是最优的。

特点

  • 强调最优性

  • 适合解决最短路径问题最小步数问题

应用场景

  • 迷宫问题。

  • 社交网络中的最短关系链。


6. 暴力搜索(Brute Force)

含义

暴力搜索是一种穷举法,通过遍历所有可能的解来寻找目标解。

特点

  • 简单直接,但效率低。

  • 适合问题规模较小的情况。

应用场景

  • 小规模组合问题(如排列、组合)。

  • 密码破解。


7. 关系图

以下是搜索算法的关系图:


8. 扩展搜索问题

状态空间搜索

  • 问题描述:在状态空间中寻找从初始状态到目标状态的路径。

  • 解决方法:DFS、BFS、A* 算法。

组合优化问题

  • 问题描述:在组合问题中寻找最优解(如旅行商问题)。

  • 解决方法:DFS + 剪枝、动态规划。

图论问题

  • 问题描述:在图中寻找路径、连通分量、最短路径等。

  • 解决方法:DFS、BFS、Dijkstra 算法。

游戏树搜索

  • 问题描述:在游戏树中寻找最优策略(如围棋、象棋)。

  • 解决方法:DFS + 剪枝、Alpha-Beta 剪枝。


9. 总结

算法/方法核心思想数据结构适用场景
深度优先搜索(DFS)尽可能深地探索某一条路径栈(递归)连通性问题、回溯问题
广度优先搜索(BFS)逐层扩展,寻找最短路径队列最短路径问题、状态空间搜索
深度优先探索探索未知问题空间,结合剪枝和回溯栈(递归)组合优化问题、游戏树搜索
广度优先探索逐层扩展,确保最优解队列最短路径问题、最小步数问题
暴力搜索穷举所有可能的解无特定数据结构小规模组合问题、密码破解

        通过理解这些搜索算法的含义、作用、区别和应用场景,可以更好地选择合适的方法解决实际问题。

三、 回溯与剪枝

1. 回溯(Backtracking)

本质

        回溯是一种系统性搜索算法,其核心思想是尝试所有可能的候选解,并在发现当前候选解不满足条件时,回退到上一步并尝试其他选择

特点

  • 递归实现:回溯通常通过递归实现,每次递归调用尝试一个候选解。

  • 状态空间树:回溯可以看作是对状态空间树的深度优先遍历。

  • 剪枝优化:通过剪枝减少不必要的搜索,提高效率。

回溯的步骤

  1. 选择:从候选解中选择一个可能的选项。

  2. 递归:基于当前选择,递归尝试下一步。

  3. 撤销:如果当前选择不满足条件,撤销选择并尝试其他选项。

代码示例(全排列问题)

void backtrack(vector<int>& nums, vector<vector<int>>& result, vector<int>& path, vector<bool>& used) {
    if (path.size() == nums.size()) {
        result.push_back(path); // 找到一个全排列
        return;
    }
    for (int i = 0; i < nums.size(); i++) {
        if (used[i]) continue; // 跳过已使用的元素
        used[i] = true; // 选择当前元素
        path.push_back(nums[i]);
        backtrack(nums, result, path, used); // 递归尝试下一步
        path.pop_back(); // 撤销选择
        used[i] = false;
    }
}

vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> result;
    vector<int> path;
    vector<bool> used(nums.size(), false);
    backtrack(nums, result, path, used);
    return result;
}

应用场景

  • 全排列、组合问题。

  • 数独、八皇后问题。

  • 子集生成、分割问题。


2. 剪枝(Pruning)

本质

        剪枝是一种优化技术,其核心思想是在搜索过程中提前排除不可能产生有效解的分支,从而减少搜索空间

特点

  • 减少搜索空间:通过剪枝,避免对无效路径的搜索。

  • 提高效率:显著降低算法的时间复杂度。

  • 结合回溯使用:剪枝通常与回溯结合使用,进一步优化回溯算法。

剪枝的实现方法

  1. 可行性剪枝:在搜索过程中,如果当前选择不满足问题的约束条件,则提前终止该分支的搜索。

  2. 最优性剪枝:在搜索过程中,如果当前选择已经不可能产生更优的解,则提前终止该分支的搜索。

代码示例(组合总和问题)

void backtrack(vector<int>& candidates, int target, vector<vector<int>>& result, vector<int>& path, int start) {
    if (target == 0) {
        result.push_back(path); // 找到一个有效组合
        return;
    }
    for (int i = start; i < candidates.size(); i++) {
        if (candidates[i] > target) continue; // 剪枝:跳过不可能的选择
        path.push_back(candidates[i]); // 选择当前元素
        backtrack(candidates, target - candidates[i], result, path, i); // 递归尝试下一步
        path.pop_back(); // 撤销选择
    }
}

vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
    vector<vector<int>> result;
    vector<int> path;
    backtrack(candidates, target, result, path, 0);
    return result;
}

应用场景

  • 组合优化问题(如背包问题、旅行商问题)。

  • 数独、八皇后问题。

  • 子集生成、分割问题。


3. 回溯与剪枝的关系

回溯和剪枝通常结合使用:

  • 回溯提供了一种系统性搜索的方法。

  • 剪枝通过减少搜索空间,优化回溯算法的效率。

关系图


4. 扩展搜索问题

数独问题

  • 问题描述:填充数独棋盘,满足每行、每列、每个 3x3 子网格都包含 1-9 的数字。

  • 解决方法:回溯 + 剪枝。

八皇后问题

  • 问题描述:在 8x8 棋盘上放置 8 个皇后,使得它们互不攻击。

  • 解决方法:回溯 + 剪枝。

组合优化问题

  • 问题描述:在给定的候选集中寻找满足条件的组合(如组合总和、子集生成)。

  • 解决方法:回溯 + 剪枝。


5. 总结

技术核心思想实现方法应用场景
回溯尝试所有可能的候选解,发现不满足条件时回退递归全排列、组合问题、数独
剪枝提前排除不可能产生有效解的分支可行性剪枝、最优性剪枝组合优化问题、八皇后问题

        通过理解回溯与剪枝的本质、作用、实现方法以及应用场景,可以更好地解决复杂的组合优化问题和搜索问题。

相关文章:

  • Elasticsearch面试宝典【刷题系列】
  • 【深度学习神经网络学习笔记(三)】向量化编程
  • 将CUBE或3DL LUT转换为PNG图像
  • 怎么修改node_modules里的文件,怎么使用patch-package修改node_modules的文件,怎么修改第三方库原文件。
  • Staruml软件的介绍安装uml类图的绘制流程
  • Go小技巧易错点100例(二十三)
  • DDR3模块、HDMI、晶振的布局原则
  • 51c视觉~CV~合集4
  • 白帽黑客系列教程之Windows驱动开发(64位环境)入门教程(七)
  • C++初阶——简单实现stack和queue
  • Linux运维——网络管理
  • 【AIGC】使用Python实现科大讯飞语音服务ASR转录功能:完整指南
  • ow rank decomposition如何用于矩阵的分解
  • 【CPP面经】大厂CPP后台开发面试经历
  • vue3:项目创建
  • Ollama部署与常用命令
  • C++初阶:C++入门基础
  • 什么是DrawCall?DrawCall为什么会影响游戏运行效率?如何减少DrawCall?
  • 如何将文件中的一部分段落整体删除
  • Grafana使用日志5--如何重置Grafana密码
  • 东莞 网站 建设 雕塑/网站搭建平台都有哪些
  • 同城信息平台推广/仓山区seo引擎优化软件
  • 专业苏州网站建设公司哪家好/中国新冠疫情最新消息
  • 布吉做棋牌网站建设/百度搜索网址
  • 佛山网站建设多少钱/排名查询系统
  • php做网站最容易/google seo是什么意思