
递归与二叉树 - C语言实现
递归与二叉树 - C语言实现
数据结构学习笔记 | 2026-05-14
第一部分:阶乘函数
一、基本概念
递归(Recursion) 是指函数在执行过程中调用自身的一种编程技巧。
- 递归的核心思想:将一个大规模问题分解为更小的同类子问题,直到问题规模小到可以直接解决
- 递归的两个必要条件:
- 终止条件(Base Case):有一个明确的终止条件,防止无限递归
- 递归调用:每次递归调用都必须使问题规模缩小
二、算法思想
阶乘的定义:
- 当 ( n = 0 ) 时,( f(0) = 1 ) (终止条件)
- 当 ( n = 1 ) 时,( f(1) = 1 )
- 当 ( n > 1 ) 时,( f(n) = n \times f(n-1) ) (递归调用)
递归执行过程
以 fac(4) 为例:
1 | fac(4) = 4 * fac(3) |
三、函数实现
1 |
|
代码解析
| 要素 | 说明 |
|---|---|
| 终止条件 | `if ((n==0) |
| 递归调用 | f = n*fac(n-1) 将问题规模从 n 减小到 n-1 |
| 返回值 | 最终返回 1×2×3×…×n 的累积结果 |
关键点:递归必须有一个明确的终止条件,且每次递归调用必须使问题规模递减,否则会导致无限递归(栈溢出)。
四、使用示例
1 | int main() |
运行结果:
1 | 4的阶乘为: 24 |
五、时间复杂度分析
| 参数 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 递归调用 n 次,每次 O(1) |
| 空间复杂度 | O(n) | 递归调用栈深度为 n |
六、关键知识点
递归必须有两个条件:
- 明确的终止条件
- 递归调用使问题规模递减
递归的代价:每次函数调用都有额外的栈空间开销,递归深度过大会导致栈溢出
递归 vs 迭代:阶乘可以用循环(迭代)实现,通常迭代比递归更高效(省去函数调用开销)
七、完整代码
第二部分:斐波那契数列
一、基本概念
斐波那契数列(Fibonacci Sequence) 是一个经典的数列,每个数等于前两个数之和。
- 递推公式:( F(n) = F(n-1) + F(n-2) )
- 起始值:( F(1) = 1, F(2) = 1 ) (也有定义 ( F(0) = 0 ))
- 数列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …
二、算法思想
斐波那契数列的递归实现遵循递归的一般原则:
- 终止条件:当 ( n < 2 ) 时,直接返回 n
- 递归调用:( f(n) = f(n-1) + f(n-2) )
递归执行过程
以 fibonacci(5) 为例:
1 | fibonacci(5) |
三、函数实现
1 |
|
代码解析
| 要素 | 说明 |
|---|---|
| 终止条件 | if (n<2) 当 n=0 或 n=1 时,返回 n 本身 |
| 递归调用 | f = fibonacci(n-1) + fibonacci(n-2) 返回前两项之和 |
| 返回值类型 | 使用 long 类型以支持较大数值 |
注意:递归实现虽然简洁,但效率较低,存在大量重复计算(如上例中 fibonacci(2) 被计算了多次)。
四、使用示例
1 | int main() |
运行结果:
1 | 请输入一个正整数: 5 |
斐波那契数列对照表:
| n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| F(n) | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 |
五、时间复杂度分析
| 参数 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(2^n) | 递归树中每个节点都展开为两个子节点,呈指数增长 |
| 空间复杂度 | O(n) | 递归调用栈的最大深度为 n |
优化方向:使用记忆化递归或**动态规划(迭代)**可将时间复杂度优化到 O(n)。
六、关键知识点
斐波那契数列的递推关系:每个数等于前两个数之和
递归终止条件:当 n < 2 时直接返回 n(F(0)=0, F(1)=1)
递归算法的问题:存在大量重复计算,时间复杂度呈指数级增长
优化思路:
- 记忆化递归:将计算过的值存储起来,避免重复计算
- 迭代动态规划:从下往上计算,只需保留前两个值
七、完整代码
第三部分:二叉树
一、基本概念
二叉树(Binary Tree) 是每个节点最多有两个子树的树结构,通常称为左孩子和右孩子。
- 节点的组成:数据域 + 左指针 + 右指针
- 二叉树的特点:
- 每个节点最多有两个子节点
- 左子树和右子树有顺序之分
- 即使只有一个子节点,也需要区分是左子树还是右子树
二叉树的分类
| 类型 | 特点 |
|---|---|
| 满二叉树 | 所有节点都有两个子节点,所有层都满 |
| 完全二叉树 | 最后一层可能不满,但节点从左到右连续 |
| 二叉搜索树 | 左子树所有节点小于根,右子树所有节点大于根 |
二、结构体定义
1 |
|
| 成员 | 含义 |
|---|---|
data |
节点存储的数据(整型) |
lchild |
指向左孩子节点的指针 |
rchild |
指向右孩子节点的指针 |
说明:
typedef为结构体定义了别名BiTNode,简化后续代码的书写。
三、函数声明
1 | int CreateBiTree(BiTNode **T); // 创建二叉树 |
四、功能函数实现
1. 创建二叉树
1 | // 创建二叉树 |
算法思想:
- 输入节点值,如果输入
-1表示该位置为空 - 否则分配内存,创建新节点
- 递归创建左子树和右子树
2. 先序遍历
1 | // 先序遍历二叉树 |
先序遍历规则:根节点 → 左子树 → 右子树
五、使用示例
1 | int main() |
示例输入输出
输入(构建如下二叉树):
1 | 1 |
1 | 请输入二叉树的根节点: |
输出:
1 | 前序遍历上面创建的二叉树: |
六、图解
1 | 示例二叉树结构: |
七、时间复杂度分析
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 创建二叉树 | O(n) | 每个节点访问一次,n 为节点数 |
| 先序遍历 | O(n) | 每个节点访问一次 |
八、关键知识点
二叉树节点的组成:数据域 + 左孩子指针 + 右孩子指针
递归构建思想:构建每个节点时,递归地创建其左子树和右子树
先序遍历的顺序:根节点 → 左子树 → 右子树(深度优先遍历的一种)
内存管理:
malloc分配节点内存,free释放(示例中未包含销毁函数)输入 -1 表示空节点:这是一种常用的二叉树构建约定