递归与回溯

本文深入探讨了递归和回溯的概念,通过LeetCode-22括号生成问题阐述了这两种算法的运用。递归是函数的层层调用,而回溯则是在尝试过程中遇到错误时返回上一步重新选择。在解决括号生成问题中,通过递归列出所有可能的组合,并利用回溯判断并排除无效的解决方案。文章强调了识别选择、限制条件和结束条件在回溯策略中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、递归 (Recursive)

我们在路上走着,前面是一个多岔路口,因为我们并不知道应该走哪条路,所以我们需要尝试。尝试的过程就是一个函数。

我们选择了一个方向,后来发现又有一个多岔路口,这时候又需要进行一次选择。所以我们需要在上一次尝试结果的基础上,再做一次尝试,即在函数内部再调用一次函数,这就是递归的过程。

二、回溯 (Backtrack)

这样重复了若干次之后,发现这次选择的这条路走不通,这时候我们知道我们上一个路口选错了,所以我们要回到上一个路口重新选择其他路,这就是回溯的思想。

回溯的思路基本如下:当前局面下,我们有若干种选择,所以我们对每一种选择进行尝试。如果发现某种选择违反了某些限定条件,此时 return;如果尝试某种选择到了最后,发现该选择是正确解,那么就将其加入到解集中。

在这种思想下,我们需要清晰的找出三个要素:选择 (Options),限制 (Restraints),结束条件 (Termination)

eg. LeetCode - 22 括号生成

递归:就是函数的一次次可能性的罗列
回溯:就是用if else语句给了多个限制条件

箭头表示函数中调用函数
从左向右越来越深

class Solution {
    List<String> res = new ArrayList<String>();
    
    public List<String> generateParenthesis(int n) {
        if (n <= 0){
            return res;
        }
        getParenthesis("", n, n);
        return res;
    }

    public void getParenthesis(String str, int left, int right){
        // 递归出口
        if (left == 0 && right == 0){
            res.add(str);
            return;
        } 
        if (left == right){
            //剩余左右括号数相等,下一个只能用左括号
            getParenthesis(str+"(", left-1, right);

        } else if (left < right){
            //剩余左括号小于右括号,下一个可以用左括号也可以用右括号
            if (left > 0){
                getParenthesis(str+"(", left-1, right);
            }
            getParenthesis(str+")", left, right-1);
        }
    }
}
### 递归回溯算法在C++中的实现及应用 #### 一、递归算法的基础概念 递归是一种函数调用自身的编程技术,在解决复杂问题时具有重要作用。通过将大问题分解成更小的子问题,递归能够简化逻辑并提高代码可读性[^1]。然而,使用递归时需注意控制递归深度以及程序运行效率。 以下是递归的一个基本例子——计算阶乘: ```cpp #include <iostream> using namespace std; int factorial(int n) { if (n == 0 || n == 1) return 1; // 终止条件 return n * factorial(n - 1); // 自身调用 } int main() { int n; cin >> n; cout << "Factorial of " << n << " is " << factorial(n) << endl; return 0; } ``` #### 二、回溯算法的核心思想 回溯法是一种系统化的试探方法,通常用于求解组合问题、排列问题以及其他约束满足问题。它基于递归的思想构建解决方案空间树,并通过剪枝操作减少不必要的搜索路径[^2]。 下面是一个经典的回溯算法实例:N皇后问题。 ```cpp #include <iostream> #include <vector> using namespace std; const int MAX_N = 20; bool board[MAX_N][MAX_N]; int countSolutions = 0; // 判断当前位置是否合法 bool isValid(int row, int col, int n) { for (int i = 0; i < row; ++i) { if (board[i][col]) return false; // 同列冲突 if ((row - i >= 0 && col - i >= 0) && board[row - i][col - i]) return false; // 左上对角线冲突 if ((row - i >= 0 && col + i < n) && board[row - i][col + i]) return false; // 右上对角线冲突 } return true; } void solveNQueens(int row, int n) { if (row == n) { // 找到一种解法 countSolutions++; return; } for (int col = 0; col < n; ++col) { if (isValid(row, col, n)) { // 如果位置有效,则尝试放置皇后 board[row][col] = true; solveNQueens(row + 1, n); // 进入下一行继续寻找解法 board[row][col] = false; // 回溯撤销当前选择 } } } int main() { int n; cin >> n; memset(board, false, sizeof(board)); solveNQueens(0, n); cout << "Total solutions: " << countSolutions << endl; return 0; } ``` #### 三、递归回溯的应用场景 递归回溯广泛应用于各种领域,例如树图的遍历、组合生成、子集枚举等问题中。以下是一些具体应用场景: 1. **树图的遍历** 使用递归可以轻松实现深度优先搜索(DFS)广度优先搜索(BFS)。对于某些特定情况下的优化需求,也可以考虑动态规划或其他非递归方法代替传统递归方案[^1]。 2. **组合排列问题** 当面对不定数量的选择项时,可以通过建立决策树模型利用回溯机制逐一探索所有可能性[^2]。 3. **子集生成** 子集问题是另一个典型的回溯案例,其中涉及从给定集合选取若干元素形成新的子集[^3]。 4. **棋盘覆盖类难题** 如八皇后问题所示,这类题目往往依赖于精确的状态表示加上高效的分支限界策略来加速收敛速度[^4]。 #### 四、注意事项 尽管递归回溯功能强大,但在实际编码过程中仍需留意潜在风险点: - 避免无限递归导致栈溢出; - 对状态转移方程进行合理设计以降低时间复杂度; - 结合实际情况灵活调整终止条件或边界处理逻辑。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

555K77

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值