> 技术文档 > 回溯、递推、递归、分治和动态规划

回溯、递推、递归、分治和动态规划


递归

函数出现调用自身的代码块就是递归,它是一种编程的实现方式

    int maxDepth(TreeNode* root) {        if(root==NULL)  return 0;        int l_depth=maxDepth(root->left);        int r_depth=maxDepth(root->right);        return max(l_depth,r_depth)+1;    }

->int  maxDepth(3)   最终返回3

->if(root==0) 3!=0

->int l_depth=maxDepth(3->left=9)        最终maxDepth(3->left=9)的返回值为1,赋值给l_depth

        ->if(root==0) 9!=0

        ->int l_depth=maxDepth(9->left=NULL)        局部变量,也就是副本,l_depth=0

             ->if(root==0)NULL==NULL   return 0  //int l_depth = maxDepth(9->left=NULL)   回溯值为0

        ->int r_depth = maxDepth(9->right=NULL)                  局部变量,也就是副本,l_depth=0

             ->if(root==0)NULL==NULL   return 0 //int r_depth = maxDepth(9->right=NULL) 回溯值为0

        return  max(0,0)+1 //int l_depth=maxDepth(9)的回溯值为1

->int r_depth=maxDepth(3->right=20) 最终返回2赋值给r_depth

        ->if(root==0)?   20!=0

        ->int l_depth=maxDepth(20->left=15)最终返回1赋值给l_depth

                ->if(15==0)?不等于

                ->int l_depth=maxDepth(15->left=NULL)        最终返回0赋值给l_depth

                        ->if(NULL==0)   return 0;

                ->int r_depth = maxDepth(15->right=NULL)          最终返回0赋值给r_depth

                        ->if(NULL==0)   return 0;

                -> return  max(0,0)+1 //int l_depth=maxDepth(15)的回溯值为1

        ->int r_depth=maxDepth(20->right=7)最终返回1赋值给r_depth

                ->if(7==0)?不等于,继续执行下一步

                ->int l_depth=maxDepth(7->left=NULL)        最终返回0赋值给l_depth

                        ->if(NULL==0)   return 0;

                ->int r_depth = maxDepth(7->right=NULL)          最终返回0赋值给r_depth

                        ->if(NULL==0)   return 0;

                -> return  max(0,0)+1 //int l_depth=maxDepth(20)的回溯值为2

        ->return max(1,1)+1 //int r_depth=maxDepth(7)的回溯值为1

->retun max(1,2)+1  //int r_depth=maxDepth(3)的回溯值为2+1=3

        


平时该咋思考呢,看原问题与子问题关系的那个return 语句,比如

int factorial(int n){if(n<=0) return 1;return n*factrial(n-1);}  

先跳到base case ,factorial(0),那么上一步就是1*factorial(0)即,1*1,从右往前看


回溯算法

回溯算法的核心是枚举所有可能解并筛选有效解

  1. 排列问题(Permutations)
    要求生成集合中元素的所有可能排列(考虑顺序)。

    • 例:[1,2,3]的全排列为[[1,2,3], [1,3,2], [2,1,3], ...]

    • 核心逻辑:每个位置尝试未使用的元素,使用后标记,回溯时撤销标记。

  2. 组合问题(Combinations)
    要求生成集合中元素的所有可能组合(不考虑顺序)。

    • 例:[1,2,3]中选 2 个元素的组合为[[1,2], [1,3], [2,3]]

    • 核心逻辑:通过 “起点索引” 避免重复(如选过 1 后,只从 2、3 开始选)。

  3. 子集问题(Subsets)
    要求生成集合的所有可能子集(包括空集和全集)。

    • 例:[1,2]的子集为[[], [1], [2], [1,2]]

    • 核心逻辑:每个元素可 “选” 或 “不选”,通过递归分支实现两种选择,回溯时无需撤销(因子集是逐步累积的)。

  4. 约束满足问题(Constraint Satisfaction Problems)
    要求在满足特定约束条件下寻找解,如:

    • 数独:每行、每列、每个 3×3 宫内数字不重复。

    • N 皇后:皇后之间不能同行、同列、同对角线。

    • 核心逻辑:通过剪枝(提前排除违反约束的路径)减少无效搜索。

  5. 路径搜索问题
    如在网格中寻找从起点到终点的所有路径(可带障碍),或迷宫求解。

    • 核心逻辑:记录当前路径,遇到死胡同则回退,尝试其他方向。

 

基础回溯算法可能因枚举过多而效率低下,实际应用中常结合以下优化方式,形成 “变体”:

  1. 带剪枝的回溯
    最常用的优化方式:在递归探索时,提前判断当前路径是否可能产生有效解,若不可能则直接终止该分支(“剪枝”),避免无效递归。

    • 例:N 皇后问题中,若当前行的某一列已被上方皇后攻击,则无需继续递归该行后续列。

  2. 记忆化回溯(回溯 + 缓存)
    对重复出现的子问题,记录其结果(缓存),避免重复计算。

    • 例:在某些复杂的组合问题中,若不同分支会遇到相同的 “剩余元素 + 约束条件”,可缓存该状态的解。

  3. 迭代式回溯(非递归回溯)
    回溯通常用递归实现,但也可通过模拟递归过程(手动维护调用栈和状态),避免递归深度过大导致的栈溢出。

    • 本质与递归回溯一致,只是实现方式从 “函数调用” 改为 “栈操作”。

  4. 分支限界回溯
    在优化问题(如求最优解)中,为每个分支设定 “下界” 或 “上界”,若当前分支的最优可能已差于已知最优解,则剪枝。

    • 例:旅行商问题(TSP)中,若当前路径长度已超过已知最短路径,则终止该分支。