每日一记:阶乘与递归

今日来整递归与阶乘,开始

目一:阶乘之和的阶乘表示判定

算法分析

  1. 问题核心
    给定一组正整数,计算其阶乘之和是否为另一个数的阶乘。例如,判断是否存在整数 mm 使得 ∑iai!=m!∑i​ai​!=m!。

  2. 涉及算法

    • 阶乘计算与累加:需计算每个输入数的阶乘并求和。

    • 阶乘查找匹配:在有限的阶乘值范围内查找是否存在与和相等的阶乘。

    • 数学优化:利用阶乘的快速增长特性缩小搜索范围。

  3. 关键步骤

    1. 计算阶乘和:遍历输入数组,计算每个数的阶乘并累加总和 SS。

    2. 预生成阶乘表:预先计算可能的 m!m!(如 m≤20m≤20,因 20!≈2.43×101820!≈2.43×1018 接近64位整数上限)。

    3. 二分查找匹配:在预生成的阶乘表中查找是否存在 m!=Sm!=S。

  4. 复杂度分析

    • 时间复杂度:O(n+k)O(n+k),其中 nn 为输入数组长度,kk 为预生成阶乘表长度(通常 k≤20k≤20)。

    • 空间复杂度:O(k)O(k),存储预生成阶乘表。

  5. 数学验证

    • 阶乘函数 m!m! 增长极快,实际可能匹配的 mm 范围很小。

    • 例:若 S=10S=10,查表可知 3!=63!=6、4!=244!=24,无解;若 S=120S=120,则 5!=1205!=120,存在解。

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/* 常量定义 */
#define MAX_FACTORIAL_INDEX 20    ///< 最大支持的阶乘数索引(对应20!)
#define MAX_INPUT_NUMBERS 100     ///< 单次最大输入数字数量

/**
 * @brief 预先生成的阶乘查询表
 * @note 索引0~20对应0!~20!的值
 * @note 20! = 2432902008176640000 (接近uint64_t最大值)
 */
const uint64_t FACTORIAL_TABLE[MAX_FACTORIAL_INDEX + 1] = {
    1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880,
    3628800, 39916800, 479001600, 6227020800, 87178291200,
    1307674368000, 20922789888000, 355687428096000,
    6402373705728000, 121645100408832000, 2432902008176640000
};

/**
 * @brief 计算单个数字的阶乘(查表法)
 * @param num 输入数字(必须满足 0 <= num <= 20)
 * @param[out] result 阶乘计算结果指针
 * @return true 计算成功
 * @return false 输入非法(输出结果不可信)
 * @warning 输入超过20将导致整数溢出
 */
bool calculate_factorial(uint8_t num, uint64_t* result) {
    // 输入参数有效性验证
    if (num > MAX_FACTORIAL_INDEX) {
        fprintf(stderr, "[错误] 输入数字%d超过最大支持值%d\n", num, MAX_FACTORIAL_INDEX);
        return false;
    }
    *result = FACTORIAL_TABLE[num];
    return true;
}

/**
 * @brief 验证数值是否为某个整数的阶乘
 * @param sum 待验证的数值
 * @param[out] m 匹配到的阶乘数(仅在返回true时有效)
 * @return true 存在满足 sum = m! 的整数m
 * @note 特殊处理0!和1!的情况
 */
bool is_factorial_number(uint64_t sum, uint8_t* m) {
    // 处理边界情况(0!和1!都等于1)
    if (sum == 0 || sum == 1) {
        *m = (sum == 0) ? 0 : 1;
        return true;
    }

    // 遍历阶乘表查找匹配项(从2!开始)
    for (uint8_t i = 2; i <= MAX_FACTORIAL_INDEX; ++i) {
        if (FACTORIAL_TABLE[i] == sum) {
            *m = i;
            return true;
        }
    }
    return false;
}

/**
 * @brief 核心验证流程:计算输入数字的阶乘和并进行验证
 * @param numbers 输入数字数组(元素范围0-20)
 * @param count 数组有效长度
 * @note 包含溢出保护机制
 */
void verify_factorial_sum(const uint8_t* numbers, size_t count) {
    uint64_t total_sum = 0; ///< 累计阶乘和

    // 逐项计算阶乘并累加
    for (size_t i = 0; i < count; ++i) {
        uint64_t fact = 0;
        if (!calculate_factorial(numbers[i], &fact)) {
            return; // 遇到错误直接终止
        }

        // 溢出保护检查(避免无符号整数回绕)
        if (total_sum > UINT64_MAX - fact) {
            fprintf(stderr, "[错误] 阶乘和超过64位无符号整数范围\n");
            return;
        }
        total_sum += fact;
    }

    // 验证结果并输出
    uint8_t m = 0;
    if (is_factorial_number(total_sum, &m)) {
        printf("√ 验证通过:sum = %lu = %u!\n", total_sum, m);
    } else {
        printf("× 验证未通过:sum = %lu 不是任何整数的阶乘\n", total_sum);
    }
}

/* 输入处理模块 */

/**
 * @brief 解析命令行参数为数字数组
 * @param argc 参数总数
 * @param argv 参数值数组
 * @param[out] numbers 输出数字缓冲区
 * @param[out] count 有效数字数量
 * @return true 解析成功
 * @note 输入格式示例:./program 2 3 5
 */
bool parse_command_line(int argc, char *argv[], uint8_t *numbers, size_t *count) {
    if (argc < 2) {
        fprintf(stderr, "用法: %s <数字1> <数字2> ... <数字N>\n", argv[0]);
        fprintf(stderr, "示例: %s 2 3 4\n", argv[0]);
        return false;
    }

    *count = 0;
    // 遍历所有参数(跳过程序名)
    for (int i = 1; i < argc && *count < MAX_INPUT_NUMBERS; i++) {
        char *endptr;
        long num = strtol(argv[i], &endptr, 10); // 十进制转换
        
        // 验证转换完整性及范围有效性
        if (*endptr != '\0' || num < 0 || num > MAX_FACTORIAL_INDEX) {
            fprintf(stderr, "[错误] 无效输入: %s (必须是0-20的整数)\n", argv[i]);
            return false;
        }
        numbers[(*count)++] = (uint8_t)num;
    }
    return true;
}

/**
 * @brief 从标准输入读取并解析数字
 * @param[out] numbers 输出数字缓冲区
 * @param[out] count 有效数字数量
 * @return true 读取到有效输入
 * @note 支持空格分隔的数字输入,例如:"2 3 5 7"
 */
bool read_from_stdin(uint8_t *numbers, size_t *count) {
    printf("请输入要计算的数字(0-20),用空格分隔(最多%d个),按回车结束:\n", MAX_INPUT_NUMBERS);
    
    char input[1024]; // 输入缓冲区
    if (fgets(input, sizeof(input), stdin) == NULL) {
        return false;
    }

    // 使用strtok分割输入字符串
    char *token = strtok(input, " \t\n"); // 支持空格/TAB分隔
    *count = 0;
    while (token != NULL && *count < MAX_INPUT_NUMBERS) {
        char *endptr;
        long num = strtol(token, &endptr, 10);
        
        // 忽略无效输入项
        if (*endptr != '\0' || num < 0 || num > MAX_FACTORIAL_INDEX) {
            fprintf(stderr, "[错误] 忽略无效输入: %s (必须是0-20的整数)\n", token);
        } else {
            numbers[(*count)++] = (uint8_t)num;
        }
        token = strtok(NULL, " \t\n");
    }
    return (*count > 0); // 至少需要1个有效输入
}

/**
 * @brief 程序主入口
 * @param argc 命令行参数个数
 * @param argv 命令行参数数组
 * @note 支持两种输入模式:
 *       1. 命令行参数模式:./program 2 3 5
 *       2. 交互式输入模式:运行后按提示输入
 */
int main(int argc, char *argv[]) {
    uint8_t numbers[MAX_INPUT_NUMBERS]; // 输入数字缓冲区
    size_t count = 0;                   // 有效数字数量

    // 输入模式选择逻辑
    if (argc > 1) {
        // 模式1:命令行参数输入
        if (!parse_command_line(argc, argv, numbers, &count)) {
            return EXIT_FAILURE;
        }
    } else {
        // 模式2:标准输入交互模式
        if (!read_from_stdin(numbers, &count)) {
            fprintf(stderr, "未提供有效输入\n");
            fprintf(stderr, "用法: %s <数字1> <数字2> ... <数字N>\n", argv[0]);
            fprintf(stderr, "或直接运行程序并按照提示输入数字\n");
            return EXIT_FAILURE;
        }
    }

    // 执行核心验证流程
    verify_factorial_sum(numbers, count);
    return EXIT_SUCCESS;
}

结果如下:


题目二:三柱汉诺塔问题

算法分析

  1. 问题核心
    将 NN 个圆盘从起始柱移动到目标柱,每次只能移动一个圆盘,且大盘不能叠在小盘上。

  2. 涉及算法

    • 递归算法:经典解法基于分治思想。

    • 迭代算法:可通过栈模拟递归过程。

    • 最小步数计算:总步数为 2N−12N−1。

  3. 递归步骤

    1. 将 N−1N−1 个盘子从起始柱通过目标柱移动到中间柱。

    2. 将第 NN 个盘子从起始柱直接移动到目标柱。

    3. 将 N−1N−1 个盘子从中间柱通过起始柱移动到目标柱。

  4. 复杂度分析

    • 时间复杂度:O(2N)O(2N),因每一步移动需递归分解。

    • 空间复杂度:O(N)O(N),递归调用栈深度为 NN。

  5. 数学验证

    • 总移动次数公式:2N−12N−1,可通过归纳法证明。

    • 例:N=3N=3 时,最少需 77 步。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <ctype.h>

/* 配置常量 */
#define MAX_DISKS 20  // 安全阈值
#define MIN_DISKS 1
#define STEP_LOG_SIZE (1 << MAX_DISKS) // 精确计算所需空间

/* 移动步骤记录 */
typedef struct {
    char from;
    char to;
} Move;

Move step_log[STEP_LOG_SIZE];
int step_count = 0;

/**
 * @brief 强化输入验证
 * @param input 输入字符串
 * @return 验证后的圆盘数量,-1表示无效
 */
int validate_input(const char* input) {
    char* endptr;
    long num = strtol(input, &endptr, 10);
    
    /* 新增:检查空白输入 */
    if (endptr == input) {
        fprintf(stderr, "[错误] 未检测到有效输入\n");
        return -1;
    }
    
    /* 检查:允许末尾空格但拒绝中间非数字字符 */
    while (*endptr != '\0') {
        if (!isspace((unsigned char)*endptr)) {
            fprintf(stderr, "[错误] 包含非法字符'%c'\n", *endptr);
            return -1;
        }
        endptr++;
    }

    if (num < MIN_DISKS || num > MAX_DISKS) {
        fprintf(stderr, "[错误] 输入必须在%d-%d之间\n", MIN_DISKS, MAX_DISKS);
        return -1;
    }
    return (int)num;
}

/**
 * @brief 经典递归解法
 */
void hanoi(int n, char src, char tar, char aux) {
    if (n == 1) {
        step_log[step_count++] = (Move){src, tar};
        return;
    }
    hanoi(n-1, src, aux, tar);
    step_log[step_count++] = (Move){src, tar};
    hanoi(n-1, aux, tar, src);
}

/**
 * @brief 解决方案入口
 */
bool solve_hanoi(int n) {
    /* 双重验证确保安全 */
    if (n < MIN_DISKS || n > MAX_DISKS) {
        fprintf(stderr, "[系统错误] 参数%d超出验证范围\n", n);
        return false;
    }

    printf("\n【汉诺塔解决方案】\n");
    printf("• 圆盘数量: %d\n• 最少需要步数: %d\n", n, (1 << n) - 1);
    
    step_count = 0; // 重置计数器
    hanoi(n, 'A', 'C', 'B');
    return true;
}

/**
 * @brief 智能输出控制
 */
void print_steps(bool verbose) {
    if (step_count == 0) {
        printf("未生成任何移动步骤\n");
        return;
    }

    printf("共需%d步移动:\n", step_count);
    if (verbose) {
        for (int i = 0; i < step_count; ++i) {
            printf("[%04d] %c → %c\n", i+1, step_log[i].from, step_log[i].to);
        }
    } else {
        printf("(完整步骤已保存到hanoi_steps.txt)\n");
        FILE* fp = fopen("hanoi_steps.txt", "w");
        if (fp) {
            for (int i = 0; i < step_count; ++i) {
                fprintf(fp, "%04d: %c → %c\n", i+1, step_log[i].from, step_log[i].to);
            }
            fclose(fp);
        } else {
            perror("无法创建输出文件");
        }
    }
}

int main() {

    char input[32];
    printf("请输入圆盘数量(%d-%d): ", MIN_DISKS, MAX_DISKS);
    if (!fgets(input, sizeof(input), stdin)) {
        fprintf(stderr, "[错误] 读取输入失败\n");
        return EXIT_FAILURE;
    }

    int n = validate_input(input);
    if (n == -1) return EXIT_FAILURE;

    if (!solve_hanoi(n)) return EXIT_FAILURE;
    
    /* 自动判断输出模式:n<=5直接显示,n>5存文件 */
    print_steps(n <= 5 || MAX_DISKS <= 5); 

    return EXIT_SUCCESS;
}

结果如下: 

        三,两题对比

对比维度题目一(阶乘和)题目二(汉诺塔)相同点
算法类型数学计算 + 查找算法递归/分治算法均需循环或递归控制流程
核心操作阶乘计算、二分查找递归移动圆盘依赖逻辑分解问题
数学模型阶乘函数 m!m! 的单调性递推公式 T(N)=2T(N−1)+1T(N)=2T(N−1)+1均基于数学规律
关键变量阶乘和 SS、预生成阶乘表圆盘数量 NN、移动步数需维护中间状态
边界条件S=0S=0 或 S=1S=1 时的特殊处理N=1N=1 时的直接移动需处理最小规模问题
输入规模影响阶乘和可能超出数值范围(需大数处理)圆盘数量 NN 过大会导致递归深度爆炸大规模输入时均需优化
时间复杂度O(n+k)O(n+k)(线性)O(2N)O(2N)(指数级)复杂度差异显著
实际应用场景密码学(特殊数值模式识别)算法教学、递归思想训练均体现基础算法的重要性
验证方法查表验证、数学归纳法数学归纳法、移动步骤模拟需通过数学证明确保正确性

总结

  1. 题目一

    • 核心是数学计算与查找,需结合阶乘的单调性和数值范围特性优化查找效率。

    • 关键挑战:处理大数溢出(如使用高精度计算库)。

  2. 题目二

    • 核心是递归思想的应用,通过分治策略将复杂问题分解为子问题。

    • 关键挑战:理解递归调用栈的运作及避免栈溢出(对大规模 NN 需改用迭代)。

  3. 共同要求

    • 对问题数学本质的深刻理解

    • 算法设计的逻辑严密性

    • 边界条件与异常输入的鲁棒性处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

司铭鸿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值