Skip to content

递归

递归是一种强大的编程技术,它允许函数调用自身来解决问题。递归将复杂问题分解为更小的、相似的子问题,直到达到一个可以直接解决的基准情况。

递归的基本概念

递归函数必须包含两个关键部分:

  1. 基准情况(Base Case):递归终止的条件,防止无限递归
  2. 递归情况(Recursive Case):函数调用自身,处理更小的子问题

递归的基本结构

c
返回类型 函数名(参数) {
    // 基准情况
    if (终止条件) {
        return 基准结果;
    }
    
    // 递归情况
    return 函数名(更小的参数);
}

简单的递归示例

阶乘计算

阶乘是递归的经典示例。n! = n × (n-1) × (n-2) × ... × 1

c
#include <stdio.h>

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

int main() {
    int num = 5;
    printf("%d! = %d\n", num, factorial(num));
    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

斐波那契数列

斐波那契数列:F(n) = F(n-1) + F(n-2),其中 F(0) = 0, F(1) = 1

c
#include <stdio.h>

int fibonacci(int n) {
    // 基准情况
    if (n <= 1) {
        return n;
    }
    
    // 递归情况
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
    int n = 10;
    printf("斐波那契数列前%d项:\n", n);
    for (int i = 0; i < n; i++) {
        printf("%d ", fibonacci(i));
    }
    printf("\n");
    return 0;
}

递归的应用场景

1. 数组求和

c
#include <stdio.h>

int arraySum(int arr[], int size) {
    // 基准情况
    if (size == 0) {
        return 0;
    }
    
    // 递归情况
    return arr[size - 1] + arraySum(arr, size - 1);
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);
    printf("数组元素之和: %d\n", arraySum(arr, size));
    return 0;
}

2. 查找数组最大值

c
#include <stdio.h>

int findMax(int arr[], int size) {
    // 基准情况
    if (size == 1) {
        return arr[0];
    }
    
    // 递归情况
    int maxRest = findMax(arr, size - 1);
    return (arr[size - 1] > maxRest) ? arr[size - 1] : maxRest;
}

int main() {
    int arr[] = {3, 7, 2, 9, 5};
    int size = sizeof(arr) / sizeof(arr[0]);
    printf("数组最大值: %d\n", findMax(arr, size));
    return 0;
}

3. 字符串反转

c
#include <stdio.h>
#include <string.h>

void reverseString(char str[], int start, int end) {
    // 基准情况
    if (start >= end) {
        return;
    }
    
    // 交换字符
    char temp = str[start];
    str[start] = str[end];
    str[end] = temp;
    
    // 递归情况
    reverseString(str, start + 1, end - 1);
}

int main() {
    char str[] = "Hello World";
    printf("原字符串: %s\n", str);
    reverseString(str, 0, strlen(str) - 1);
    printf("反转后: %s\n", str);
    return 0;
}

4. 判断回文

c
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

bool isPalindrome(char str[], int start, int end) {
    // 基准情况
    if (start >= end) {
        return true;
    }
    
    // 如果首尾字符不匹配
    if (str[start] != str[end]) {
        return false;
    }
    
    // 递归情况
    return isPalindrome(str, start + 1, end - 1);
}

int main() {
    char str1[] = "racecar";
    char str2[] = "hello";
    
    printf("\"%s\" 是回文吗? %s\n", str1, 
           isPalindrome(str1, 0, strlen(str1) - 1) ? "是" : "否");
    printf("\"%s\" 是回文吗? %s\n", str2, 
           isPalindrome(str2, 0, strlen(str2) - 1) ? "是" : "否");
    return 0;
}

5. 汉诺塔问题

汉诺塔是递归的经典问题,将n个盘子从源柱子移动到目标柱子,借助辅助柱子。

c
#include <stdio.h>

void hanoi(int n, char from, char to, char aux) {
    // 基准情况
    if (n == 1) {
        printf("将盘子 1 从 %c 移动到 %c\n", from, to);
        return;
    }
    
    // 递归情况
    // 将n-1个盘子从源柱子移动到辅助柱子
    hanoi(n - 1, from, aux, to);
    
    // 将第n个盘子从源柱子移动到目标柱子
    printf("将盘子 %d%c 移动到 %c\n", n, from, to);
    
    // 将n-1个盘子从辅助柱子移动到目标柱子
    hanoi(n - 1, aux, to, from);
}

int main() {
    int n = 3;
    printf("汉诺塔问题(%d个盘子)的解法:\n", n);
    hanoi(n, 'A', 'C', 'B');
    return 0;
}

6. 二分查找

c
#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, left, mid - 1, target);
    } else {
        return binarySearch(arr, mid + 1, right, target);
    }
}

int main() {
    int arr[] = {1, 3, 5, 7, 9, 11, 13, 15};
    int size = sizeof(arr) / sizeof(arr[0]);
    int target = 7;
    
    int result = binarySearch(arr, 0, size - 1, target);
    if (result != -1) {
        printf("元素 %d 在数组中的索引: %d\n", target, result);
    } else {
        printf("元素 %d 不在数组中\n", target);
    }
    return 0;
}

7. 最大公约数(GCD)

使用欧几里得算法计算最大公约数。

c
#include <stdio.h>

int gcd(int a, int b) {
    // 基准情况
    if (b == 0) {
        return a;
    }
    
    // 递归情况
    return gcd(b, a % b);
}

int main() {
    int num1 = 48, num2 = 18;
    printf("%d%d 的最大公约数: %d\n", num1, num2, gcd(num1, num2));
    return 0;
}

8. 幂运算

c
#include <stdio.h>

double power(double base, int exponent) {
    // 基准情况
    if (exponent == 0) {
        return 1;
    }
    
    // 处理负指数
    if (exponent < 0) {
        return 1.0 / power(base, -exponent);
    }
    
    // 递归情况
    double half = power(base, exponent / 2);
    
    if (exponent % 2 == 0) {
        return half * half;
    } else {
        return base * half * half;
    }
}

int main() {
    double base = 2.0;
    int exponent = 10;
    printf("%.2f%d 次方: %.2f\n", base, exponent, power(base, exponent));
    return 0;
}

尾递归

尾递归是指递归调用是函数中的最后一个操作。尾递归可以被编译器优化为循环,避免栈溢出。

普通递归阶乘

c
int factorial(int n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);  // 不是尾递归,因为乘法在递归调用之后
}

尾递归阶乘

c
#include <stdio.h>

int factorialHelper(int n, int accumulator) {
    // 基准情况
    if (n <= 1) {
        return accumulator;
    }
    
    // 递归情况(尾递归)
    return factorialHelper(n - 1, n * accumulator);
}

int factorial(int n) {
    return factorialHelper(n, 1);
}

int main() {
    int num = 5;
    printf("%d! = %d\n", num, factorial(num));
    return 0;
}

递归 vs 迭代

递归实现

c
int factorialRecursive(int n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorialRecursive(n - 1);
}

迭代实现

c
int factorialIterative(int n) {
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

优缺点对比

特性递归迭代
代码简洁性简洁优雅相对复杂
理解难度容易理解需要跟踪状态
内存使用栈空间较大内存效率高
执行效率可能有函数调用开销通常更快
适用场景树形结构、分治问题线性问题、性能敏感

递归的注意事项

1. 栈溢出

递归深度过大会导致栈溢出。

c
// 危险:可能导致栈溢出
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

// 对于很大的n,这会导致栈溢出
factorial(10000);  // 可能崩溃

2. 性能问题

某些递归算法效率低下,如斐波那契数列的简单实现。

c
// 低效的实现:重复计算
int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// 优化:使用记忆化
#include <stdio.h>

#define MAX 100

int memo[MAX];

void initMemo() {
    for (int i = 0; i < MAX; i++) {
        memo[i] = -1;
    }
}

int fibonacciMemo(int n) {
    if (n <= 1) return n;
    
    if (memo[n] != -1) {
        return memo[n];
    }
    
    memo[n] = fibonacciMemo(n - 1) + fibonacciMemo(n - 2);
    return memo[n];
}

int main() {
    initMemo();
    printf("斐波那契数列第50项: %d\n", fibonacciMemo(50));
    return 0;
}

3. 必须有基准情况

没有基准情况会导致无限递归。

c
// 错误:没有基准情况
int infiniteRecursion(int n) {
    return infiniteRecursion(n - 1);  // 永远不会停止
}

// 正确:有基准情况
int correctRecursion(int n) {
    if (n <= 0) return 0;  // 基准情况
    return n + correctRecursion(n - 1);
}

递归的高级应用

1. 全排列

c
#include <stdio.h>
#include <string.h>

void swap(char *a, char *b) {
    char temp = *a;
    *a = *b;
    *b = temp;
}

void permute(char str[], int l, int r) {
    // 基准情况
    if (l == r) {
        printf("%s\n", str);
        return;
    }
    
    // 递归情况
    for (int i = l; i <= r; i++) {
        swap(&str[l], &str[i]);
        permute(str, l + 1, r);
        swap(&str[l], &str[i]);  // 回溯
    }
}

int main() {
    char str[] = "ABC";
    printf("字符串 \"%s\" 的全排列:\n", str);
    permute(str, 0, strlen(str) - 1);
    return 0;
}

2. 二叉树遍历

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

typedef struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;

TreeNode* createNode(int data) {
    TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
    node->data = data;
    node->left = NULL;
    node->right = NULL;
    return node;
}

// 前序遍历
void preorder(TreeNode* root) {
    if (root == NULL) return;
    printf("%d ", root->data);
    preorder(root->left);
    preorder(root->right);
}

// 中序遍历
void inorder(TreeNode* root) {
    if (root == NULL) return;
    inorder(root->left);
    printf("%d ", root->data);
    inorder(root->right);
}

// 后序遍历
void postorder(TreeNode* root) {
    if (root == NULL) return;
    postorder(root->left);
    postorder(root->right);
    printf("%d ", root->data);
}

int main() {
    TreeNode* root = createNode(1);
    root->left = createNode(2);
    root->right = createNode(3);
    root->left->left = createNode(4);
    root->left->right = createNode(5);

    printf("前序遍历: ");
    preorder(root);
    printf("\n");

    printf("中序遍历: ");
    inorder(root);
    printf("\n");

    printf("后序遍历: ");
    postorder(root);
    printf("\n");

    return 0;
}

3. 快速排序

c
#include <stdio.h>

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

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++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    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() {
    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;
}

总结

递归是一种强大的编程技术,具有以下特点:

优点:

  • 代码简洁、优雅
  • 适合处理树形结构和分治问题
  • 更接近数学定义
  • 易于理解和维护

缺点:

  • 可能导致栈溢出
  • 函数调用开销较大
  • 某些情况下性能不如迭代
  • 需要仔细设计基准情况

适用场景:

  • 树和图的遍历
  • 分治算法(如快速排序、归并排序)
  • 数学问题的递归定义
  • 回溯算法
  • 动态规划

掌握递归对于成为一名优秀的程序员非常重要,它能够帮助你以更抽象和优雅的方式解决复杂问题。