第二章:递归(Recursion)
一、 什么是递归 (知识点)
1. 递归的定义
- 概念:在定义一个过程或函数时出现调用本过程或本函数的成分,称为递归。
- 分类:调用自身称为直接递归;过程p调用q,q又调用p称为间接递归(任何间接递归都可等价转换为直接递归)。
- 尾递归:如果递归调用语句是函数中的最后一条执行语句,则称为尾递归。
2. 使用递归解决问题的三个条件
- 问题可以转化为一个或多个子问题求解,且子问题的求解方法与原问题完全相同,只是在数量规模上不同。
- 递归调用的次数必须是有限的。
- 必须有结束递归的条件(递归出口)来终止递归。
3. 何时使用递归(三大场景)
- 定义的数学公式是递归的:如求阶乘 、Fibonacci数列、Ackerman函数。
- 数据结构是递归的:如单链表(结点中包含指向自身类型的指针)、二叉树。
- 问题的求解方法是递归的:如Hanoi塔问题。
4. 递归模型与执行过程
- 递归模型由两部分组成:
- 递归出口:确定递归到何时结束(如 )。
- 递归体:确定递归求解时的递推关系,即将“大问题”转化为“小问题”的组合(如 )。
- 底层执行过程:系统利用栈(系统栈)来为每一次调用开辟存储单元,保存返回地址和被中断函数的参量值。执行分为两步:分解过程(大问题化小问题直到出口)和求值过程(退栈计算并返回结果)。递归深度越深,开辟的栈空间越大。
二、 递归算法设计方法与经典例题
设计步骤:①抽象出合理的小问题 ;②在此基础上确定 的解与 的关系;③确定特定情况下的解作为递归出口。
例题 1:求数组的最大元素
- 题意:求包含 个元素的整数数组 的最大元素。
- 推导思路 (方法一):将问题分为前 个元素的最大值(小问题)和第 个元素。递推关系为 ,出口为 时返回 。
- 推导思路 (方法二:分治思想):采用二分法,求出左半段最大值 和右半段最大值 ,取两者的较大者。出口为 (只有一个元素)时返回 。
例题 2:递归数据结构的算法设计(单链表)
- 题意1(释放单链表所有结点):
- 推导:要释放首结点为 的单链表,先递归释放以 为首的剩余链表,然后再释放 结点本身(
free(L))。出口是 时不做任何事。
- 推导:要释放首结点为 的单链表,先递归释放以 为首的剩余链表,然后再释放 结点本身(
- 题意2(逆序输出单链表的值):
- 推导:要逆序输出,必须先通过递归调用
OutputFromTail(L->next)输出后续结点,递归调用返回后,再执行printf(L->data)输出当前结点的值。出口为 的取反。
- 推导:要逆序输出,必须先通过递归调用
例题 3:由先序和中序序列创建二叉树
- 题意:给定二叉树的先序序列 和中序序列 ,构造二叉链表。
- 推导:
- 先序序列的第一个元素 必定是根结点。
- 在中序序列 中找到 的位置 (即 )。
- 由此可知,中序序列中 为左子树(共 个结点), 为右子树(共 个结点)。
- 递归创建左子树
CreateBTree(pre+1, in, k)和右子树CreateBTree(pre+k+1, in+k+1, n-k-1),分别挂在根结点的左右指针上。
例题 4:集合的全排列问题
- 题意:求包含 个元素的集合 的所有排列。
- 推导:设 为集合 的全排列。当 时 。当 时,全排列由分别以每个元素 作为前缀,加上剩余元素集合 的全排列构成,即组合 。
例题 5:整数划分问题(⭐️极高频考点)
- 题意:将正整数 无序拆分成最大加数不超过 的几种方案,记为 。
- 推导(分五种情况讨论):
- 当 或 时:无论对方是多少,都只有 1 种划分(全由1组成或本身为1),。
- 当 时:最大加数不可能超过 ,所以等价于 。
- 当 时:划分方案中包含 的只有1种(即 本身);不包含 的方案最大加数不超过 。故 。
- 当 时:划分为两部分。包含至少一个 的情况,相当于把剩下的 继续用不超过 的数划分,即 ;绝对不包含 的情况,相当于用不超过 的数划分,即 。两者相加得 。
- 例子计算:。
例题 6:递归的简单选择排序与冒泡排序
- 简单选择排序推导:在序列 中找最小元素 ,将其与 交换。然后对剩下的 递归调用
SelectSort(a, i+1, n)。出口是 。 - 冒泡排序推导:将前 个元素中的最大值通过相邻交换冒泡到最后。然后递归调用
BubbleSort(a, i+1, n)对剩下的元素排序。
三、 递归转化为非递归
- 直接转化法:直接用循环结构的算法替代递归(不需要栈)。如阶乘的循环实现。
- 间接转化法:用栈模拟系统底层运行过程,保存必须的信息。如树、图的遍历。
四、 递归算法复杂度分析(四大方法)
递归算法的执行时间 可用递归方程描述,常用以下四种方法求解:
1. 替换法
- 原理:将递归公式不断展开、代换子问题的规模,通过多项式整理推导解。
- 例题:汉诺塔:。展开为 ……最终化简为 。
- 例题:二路归并排序:。展开为 ,推导 次后令 ,得 。
2. 特征方程法
- 原理:用于解 阶常系数线性齐次/非齐次递归方程。
- 根不相同:若有 个不同根 ,通解为 。
- 有重根:若有 个重根,通解会带有 的多项式系数,如 。
- 例题 1:,初始 。
- 推导:特征方程 ,因式分解得根 。
- 通解为 。代入初始条件解得 。最终解为 。
- 例题 2(Fibonacci数列):。特征方程 ,解得根为 ,代入初始值得解 。
3. 递归树法
- 原理:展开递归方程构造递归树,把每一层的时间进行求和得出估计。
- 例题 1:。第一层代价 ,第二层 ,第三层 。求和得 。
- 例题 2(不平衡树):。每层的和均为 。最长路径高度为 。总和等于树高乘层和,即 。
4. 主方法 (Master Method)
- 原理:针对形式为 的方程()。
- 三大情况(通过比较 与 ):
- 若 大(呈多项式大于),则 。
- 若 与 一样大(同阶),则 。
- 若 更大且满足正则条件,则 。
- 例题 1:。。计算 。因为 比 大,属情况1,故 。
- 例题 2:。。计算 。因为 比 小,属情况3,故 。