递归函数

函数的自我调用

递归函数是函数直接或间接调用自身的编程技术。东巴文(db-w.cn) 将带你深入理解递归函数,掌握递归编程的核心技能。

💡 东巴文观点:递归是将复杂问题分解为简单问题的艺术,通过函数自我调用实现问题的自我相似性解决。

递归概述

什么是递归?

东巴文说明:递归是函数直接或间接调用自身的过程。

东巴文递归结构:

递归函数
├── 基准情况(Base Case)
│   └── 终止递归的条件
├── 递归情况(Recursive Case)
│   └── 函数调用自身
└── 递归调用(Recursive Call)
    └── 向基准情况靠近

递归的基本结构

返回类型 函数名(参数) {
    if (基准情况) {
        // 返回结果,不再递归
        return 结果;
    } else {
        // 递归调用
        return 函数名(更小的参数);
    }
}

第一个递归函数

#include <stdio.h>

// 递归函数:倒数
void countdown(int n) {
    // 基准情况
    if (n <= 0) {
        printf("发射!\n");
        return;
    }
    
    // 递归情况
    printf("%d\n", n);
    countdown(n - 1);  // 递归调用
}

int main() {
    printf("=== 东巴文第一个递归函数 ===\n\n");
    
    countdown(5);
    
    return 0;
}

东巴文执行过程:

countdown(5)
  打印 5
  countdown(4)
    打印 4
    countdown(3)
      打印 3
      countdown(2)
        打印 2
        countdown(1)
          打印 1
          countdown(0)
            打印 "发射!"
            返回
          返回
        返回
      返回
    返回
  返回

递归的经典应用

1. 阶乘

#include <stdio.h>

// 递归计算阶乘
long factorial(int n) {
    // 基准情况
    if (n <= 1) {
        return 1;
    }
    
    // 递归情况
    return n * factorial(n - 1);
}

// 迭代计算阶乘
long factorial_iterative(int n) {
    long result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

int main() {
    printf("=== 东巴文阶乘递归 ===\n\n");
    
    printf("递归方法:\n");
    printf("5! = %ld\n", factorial(5));
    printf("10! = %ld\n", factorial(10));
    
    printf("\n迭代方法:\n");
    printf("5! = %ld\n", factorial_iterative(5));
    printf("10! = %ld\n", factorial_iterative(10));
    
    return 0;
}

东巴文阶乘递归过程:

factorial(5)
= 5 * factorial(4)
= 5 * 4 * factorial(3)
= 5 * 4 * 3 * factorial(2)
= 5 * 4 * 3 * 2 * factorial(1)
= 5 * 4 * 3 * 2 * 1
= 120

2. 斐波那契数列

#include <stdio.h>

// 递归计算斐波那契数列
long fibonacci(int n) {
    // 基准情况
    if (n <= 1) {
        return n;
    }
    
    // 递归情况
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// 迭代计算斐波那契数列
long fibonacci_iterative(int n) {
    if (n <= 1) return n;
    
    long a = 0, b = 1, c;
    for (int i = 2; i <= n; i++) {
        c = a + b;
        a = b;
        b = c;
    }
    return b;
}

int main() {
    printf("=== 东巴文斐波那契递归 ===\n\n");
    
    printf("递归方法:\n");
    printf("斐波那契数列前10项:\n");
    for (int i = 0; i < 10; i++) {
        printf("%ld ", fibonacci(i));
    }
    printf("\n");
    
    printf("\n迭代方法:\n");
    printf("斐波那契数列前10项:\n");
    for (int i = 0; i < 10; i++) {
        printf("%ld ", fibonacci_iterative(i));
    }
    printf("\n");
    
    return 0;
}

3. 汉诺塔

#include <stdio.h>

// 汉诺塔递归解法
void hanoi(int n, char from, char to, char aux) {
    // 基准情况
    if (n == 1) {
        printf("移动盘子1从%c到%c\n", from, to);
        return;
    }
    
    // 递归情况
    hanoi(n - 1, from, aux, to);  // 将n-1个盘子从from移到aux
    printf("移动盘子%d从%c到%c\n", n, from, to);  // 移动第n个盘子
    hanoi(n - 1, aux, to, from);  // 将n-1个盘子从aux移到to
}

int main() {
    printf("=== 东巴文汉诺塔递归 ===\n\n");
    
    printf("3个盘子的汉诺塔:\n");
    hanoi(3, 'A', 'C', 'B');
    
    return 0;
}

4. 二分查找

#include <stdio.h>

// 递归二分查找
int binarySearch(int arr[], int left, int right, int target) {
    // 基准情况:未找到
    if (left > right) {
        return -1;
    }
    
    int mid = left + (right - left) / 2;
    
    // 基准情况:找到
    if (arr[mid] == target) {
        return mid;
    }
    
    // 递归情况
    if (arr[mid] < target) {
        return binarySearch(arr, mid + 1, right, target);  // 搜索右半部分
    } else {
        return binarySearch(arr, left, mid - 1, target);  // 搜索左半部分
    }
}

int main() {
    printf("=== 东巴文二分查找递归 ===\n\n");
    
    int arr[] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};
    int size = sizeof(arr) / sizeof(arr[0]);
    
    int target = 7;
    int index = binarySearch(arr, 0, size - 1, target);
    
    if (index != -1) {
        printf("找到%d,位置:%d\n", target, index);
    } else {
        printf("未找到%d\n", target);
    }
    
    return 0;
}

5. 最大公约数

#include <stdio.h>

// 递归计算最大公约数(欧几里得算法)
int gcd(int a, int b) {
    // 基准情况
    if (b == 0) {
        return a;
    }
    
    // 递归情况
    return gcd(b, a % b);
}

// 迭代计算最大公约数
int gcd_iterative(int a, int b) {
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

int main() {
    printf("=== 东巴文最大公约数递归 ===\n\n");
    
    printf("递归方法:\n");
    printf("gcd(48, 18) = %d\n", gcd(48, 18));
    printf("gcd(56, 42) = %d\n", gcd(56, 42));
    
    printf("\n迭代方法:\n");
    printf("gcd(48, 18) = %d\n", gcd_iterative(48, 18));
    printf("gcd(56, 42) = %d\n", gcd_iterative(56, 42));
    
    return 0;
}

递归的优缺点

优点

东巴文递归优点表:

优点 说明 东巴文解释
代码简洁 递归代码通常更简洁 易于理解
问题分解 将复杂问题分解为简单问题 分而治之
自然表达 某些问题天然适合递归 如树遍历
减少代码量 避免重复代码 提高效率

缺点

东巴文递归缺点表:

缺点 说明 东巴文解释
效率较低 递归调用有额外开销 占用栈空间
栈溢出 深度递归可能导致栈溢出 需要限制深度
难以调试 递归调用链复杂 不易追踪
重复计算 可能重复计算相同子问题 需要优化

递归 vs 迭代

性能对比

#include <stdio.h>
#include <time.h>

// 递归计算斐波那契
long fib_recursive(int n) {
    if (n <= 1) return n;
    return fib_recursive(n - 1) + fib_recursive(n - 2);
}

// 迭代计算斐波那契
long fib_iterative(int n) {
    if (n <= 1) return n;
    
    long a = 0, b = 1, c;
    for (int i = 2; i <= n; i++) {
        c = a + b;
        a = b;
        b = c;
    }
    return b;
}

int main() {
    printf("=== 东巴文递归 vs 迭代性能对比 ===\n\n");
    
    int n = 35;
    clock_t start, end;
    
    // 递归方法
    start = clock();
    long result1 = fib_recursive(n);
    end = clock();
    printf("递归方法:fib(%d) = %ld\n", n, result1);
    printf("耗时:%.3f秒\n", (double)(end - start) / CLOCKS_PER_SEC);
    
    // 迭代方法
    start = clock();
    long result2 = fib_iterative(n);
    end = clock();
    printf("\n迭代方法:fib(%d) = %ld\n", n, result2);
    printf("耗时:%.3f秒\n", (double)(end - start) / CLOCKS_PER_SEC);
    
    return 0;
}

东巴文对比表:

特性 递归 迭代 东巴文说明
代码简洁性 简洁 复杂 递归更简洁
执行效率 较低 较高 迭代更高效
内存占用 较大 较小 迭代更节省
栈溢出风险 有 无 迭代更安全
适用场景 问题自相似 线性处理 根据问题选择

递归的优化

1. 尾递归优化

东巴文说明:尾递归是递归调用在函数的最后一步,编译器可以优化为迭代。

#include <stdio.h>

// 普通递归
long factorial_normal(int n) {
    if (n <= 1) return 1;
    return n * factorial_normal(n - 1);  // 不是尾递归
}

// 尾递归
long factorial_tail(int n, long accumulator) {
    if (n <= 1) return accumulator;
    return factorial_tail(n - 1, n * accumulator);  // 尾递归
}

// 包装函数
long factorial(int n) {
    return factorial_tail(n, 1);
}

int main() {
    printf("=== 东巴文尾递归优化 ===\n\n");
    
    printf("普通递归:5! = %ld\n", factorial_normal(5));
    printf("尾递归:5! = %ld\n", factorial(5));
    
    return 0;
}

2. 记忆化递归

东巴文说明:记忆化递归使用缓存存储已计算的结果,避免重复计算。

#include <stdio.h>

#define MAX_N 100

// 记忆化缓存
long cache[MAX_N] = {0};

// 记忆化递归斐波那契
long fib_memoization(int n) {
    // 基准情况
    if (n <= 1) return n;
    
    // 检查缓存
    if (cache[n] != 0) {
        return cache[n];
    }
    
    // 计算并缓存
    cache[n] = fib_memoization(n - 1) + fib_memoization(n - 2);
    return cache[n];
}

int main() {
    printf("=== 东巴文记忆化递归 ===\n\n");
    
    printf("斐波那契数列前20项:\n");
    for (int i = 0; i < 20; i++) {
        printf("%ld ", fib_memoization(i));
    }
    printf("\n");
    
    return 0;
}

3. 限制递归深度

#include <stdio.h>
#include <stdlib.h>

#define MAX_DEPTH 1000

// 限制递归深度
int recursiveFunction(int n, int depth) {
    // 检查递归深度
    if (depth > MAX_DEPTH) {
        printf("错误:递归深度超过限制\n");
        return -1;
    }
    
    // 基准情况
    if (n <= 0) {
        return 0;
    }
    
    // 递归情况
    return n + recursiveFunction(n - 1, depth + 1);
}

int main() {
    printf("=== 东巴文限制递归深度 ===\n\n");
    
    int result = recursiveFunction(100, 0);
    printf("结果:%d\n", result);
    
    return 0;
}

递归的应用场景

1. 树的遍历

#include <stdio.h>
#include <stdlib.h>

// 树节点
struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
};

// 创建树节点
struct TreeNode* createNode(int data) {
    struct TreeNode *node = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    node->data = data;
    node->left = NULL;
    node->right = NULL;
    return node;
}

// 前序遍历(递归)
void preorderTraversal(struct TreeNode *root) {
    if (root == NULL) return;
    
    printf("%d ", root->data);  // 访问根节点
    preorderTraversal(root->left);  // 遍历左子树
    preorderTraversal(root->right);  // 遍历右子树
}

// 中序遍历(递归)
void inorderTraversal(struct TreeNode *root) {
    if (root == NULL) return;
    
    inorderTraversal(root->left);  // 遍历左子树
    printf("%d ", root->data);  // 访问根节点
    inorderTraversal(root->right);  // 遍历右子树
}

// 后序遍历(递归)
void postorderTraversal(struct TreeNode *root) {
    if (root == NULL) return;
    
    postorderTraversal(root->left);  // 遍历左子树
    postorderTraversal(root->right);  // 遍历右子树
    printf("%d ", root->data);  // 访问根节点
}

int main() {
    printf("=== 东巴文树的递归遍历 ===\n\n");
    
    // 构建树
    struct TreeNode *root = createNode(1);
    root->left = createNode(2);
    root->right = createNode(3);
    root->left->left = createNode(4);
    root->left->right = createNode(5);
    
    printf("前序遍历:");
    preorderTraversal(root);
    printf("\n");
    
    printf("中序遍历:");
    inorderTraversal(root);
    printf("\n");
    
    printf("后序遍历:");
    postorderTraversal(root);
    printf("\n");
    
    return 0;
}

2. 快速排序

#include <stdio.h>

// 分区函数
int partition(int arr[], int low, int high) {
    int pivot = arr[high];
    int i = low - 1;
    
    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) {
            i++;
            // 交换arr[i]和arr[j]
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    
    // 交换arr[i+1]和arr[high]
    int temp = arr[i + 1];
    arr[i + 1] = arr[high];
    arr[high] = temp;
    
    return i + 1;
}

// 快速排序(递归)
void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        
        quickSort(arr, low, pi - 1);  // 排序左半部分
        quickSort(arr, pi + 1, high);  // 排序右半部分
    }
}

int main() {
    printf("=== 东巴文快速排序递归 ===\n\n");
    
    int arr[] = {10, 7, 8, 9, 1, 5};
    int size = sizeof(arr) / sizeof(arr[0]);
    
    printf("原数组:");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    quickSort(arr, 0, size - 1);
    
    printf("排序后:");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    return 0;
}

东巴文最佳实践

1. 确保有基准情况

// ✅ 正确:有基准情况
int factorial(int n) {
    if (n <= 1) return 1;  // 基准情况
    return n * factorial(n - 1);
}

// ❌ 错误:没有基准情况
int factorial_bad(int n) {
    return n * factorial_bad(n - 1);  // 无限递归
}

2. 确保向基准情况靠近

// ✅ 正确:向基准情况靠近
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);  // n-1向基准情况靠近
}

// ❌ 错误:不向基准情况靠近
int factorial_bad(int n) {
    if (n <= 1) return 1;
    return n * factorial_bad(n);  // n不变,不会到达基准情况
}

3. 避免重复计算

// ❌ 错误:重复计算
int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);  // 大量重复计算
}

// ✅ 正确:使用记忆化
int cache[100] = {0};
int fib_memo(int n) {
    if (n <= 1) return n;
    if (cache[n] != 0) return cache[n];
    cache[n] = fib_memo(n - 1) + fib_memo(n - 2);
    return cache[n];
}

4. 考虑使用迭代

// 递归:代码简洁但效率低
int factorial_recursive(int n) {
    if (n <= 1) return 1;
    return n * factorial_recursive(n - 1);
}

// 迭代:效率更高
int factorial_iterative(int n) {
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

东巴文验证清单

完成本章学习后,请确认:

  • 理解递归的概念
  • 掌握递归的基本结构
  • 掌握递归的经典应用
  • 了解递归的优缺点
  • 理解递归与迭代的区别
  • 掌握递归的优化方法
  • 了解递归的应用场景
  • 掌握递归的最佳实践

下一步学习

掌握递归函数后,你可以继续学习:

如果遇到问题,欢迎访问 东巴文(db-w.cn) 获取帮助!


东巴文(db-w.cn) - 让编程学习更简单

📦 东巴文递归函数提示:递归是解决问题的优雅方式,掌握递归是成为优秀程序员的关键。在 db-w.cn,我们会通过大量实例帮你掌握递归函数的使用!