递归与回溯

递归

程序调用自身的编程技巧称为递归( recursion)。 递归做为一种算法在程序设计语言中广泛应用。
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

通常来说,为了描述问题的某一状态,必须用到该状态的上一个状态;而如果要描述上一个状态,又必须用到上一个状态的上一个状态…… 这样用自己来定义自己的方法就是递归。

回溯

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。

回溯的思路基本如下:当前局面下,我们有若干种选择,所以我们对每一种选择进行尝试。如果发现某种选择违反了某些限定条件,此时 return;如果尝试某种选择到了最后,发现该选择是正确解,那么就将其加入到解集中。
在这种思想下,我们需要清晰的找出三个要素:选择 (Options),限制 (Restraints),结束条件 (Termination)。

回溯算法框架

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    
    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,特别简单。

递归与回溯的区别

递归是一种算法结构。递归会出现在子程序中,形式上表现为直接或间接的自己调用自己。典型的例子是阶乘,计算规律为:n!=n×(n−1)!

回溯是一种算法思想,它是用递归实现的。回溯的过程类似于穷举法,但回溯有“剪枝”功能,即自我判断过程。例如有求和问题,给定有 7 个元素的组合 [1, 2, 3, 4, 5, 6, 7],求加和为 7 的子集。累加计算中,选择 1+2+3+4 时,判断得到结果为 10 大于 7,那么后面的 5, 6, 7 就没有必要计算了。这种方法属于搜索过程中的优化,即“剪枝”功能。

### 递归回溯算法在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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值