C 练习实例13 – 水仙花数(手把手讲解)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

在编程学习的旅程中,基础练习实例是巩固知识的重要途径。"C 练习实例13 – 水仙花数"作为经典的入门题目,不仅考察了基础语法的掌握,还通过数学问题与编程逻辑的结合,帮助开发者理解如何将抽象概念转化为代码。本文将从数学定义、实现思路、代码示例到进阶优化,逐步解析这一问题,并通过形象比喻和实际案例,让读者既能理解核心逻辑,又能掌握代码实现的关键技巧。


水仙花数的数学定义与直观理解

数学背景

水仙花数(Narcissistic Number),又称自恋数或超完全数字不变数,是指一个三位正整数,其各位数字的立方和等于该数本身。例如,153是一个水仙花数,因为:
$$1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153$$

形象比喻

想象将数字拆解为“积木块”:

  • 拆解过程:将三位数分解为百位、十位、个位,如同将积木块逐一分开。
  • 重组逻辑:计算每个“积木块”的立方值,再将它们“拼合”成总和,最后对比原数是否匹配。

通过这种比喻,可以快速理解问题的核心:如何高效地分解数字并验证条件


C语言实现的基本思路

问题拆解步骤

  1. 遍历范围:水仙花数是三位数,因此遍历100到999之间的所有整数。
  2. 分解位数:对每个数n,提取其百位、十位、个位。
  3. 计算立方和:将各位数的立方相加,得到总和sum
  4. 判断条件:若sum == n,则输出该数。

代码实现关键点

1. 分解数字的技巧

分解三位数的方法可通过数学运算实现:

  • 个位n % 10
  • 十位(n / 10) % 10
  • 百位n / 100

2. 立方和的计算

直接使用乘法运算符*,避免浮点误差:

sum = (hundred * hundred * hundred) +  
      (ten * ten * ten) +  
      (unit * unit * unit);

基础代码示例与详细解析

#include <stdio.h>  

int main() {  
    int n, hundred, ten, unit, sum;  
    // 遍历所有三位数  
    for(n = 100; n <= 999; n++) {  
        // 分解百位、十位、个位  
        hundred = n / 100;  
        ten = (n / 10) % 10;  
        unit = n % 10;  
        // 计算立方和  
        sum = hundred*hundred*hundred +  
              ten*ten*ten +  
              unit*unit*unit;  
        // 判断并输出结果  
        if(sum == n) {  
            printf("%d 是水仙花数\n", n);  
        }  
    }  
    return 0;  
}  

代码逐行解析

  1. 变量声明

    • hundred, ten, unit 分别存储百位、十位、个位。
    • sum 用于累加各位的立方值。
  2. 循环范围

    • for(n = 100; n <= 999; n++) 确保只遍历三位数。
  3. 位数分解逻辑

    • hundred = n / 100:例如,当n=153时,153/100=1,得到百位1
    • ten = (n/10)%10153/10=15,取模105,即十位。
    • unit = n%10:直接取余,得到个位3
  4. 立方和计算

    • 使用连续乘法而非pow函数,避免浮点精度问题。
  5. 条件判断

    • sum == n时,输出结果。

优化与扩展:提升代码效率与可读性

优化方向一:减少计算量

问题:当前代码对每个数都进行分解和立方计算,但部分步骤可提前优化。
改进方案

  • 提前终止循环:当sum超过当前n时,可提前跳出循环(需按特定顺序遍历)。
  • 数学预判:例如,百位至少为1,因此立方和最小为1^3 + 0^3 + 0^3 = 1,但三位数最小为100,因此实际无需额外预判。

优化方向二:函数封装

将分解和计算逻辑封装为函数,提升代码复用性:

#include <stdio.h>  

// 函数:计算数字各位的立方和  
int calculate_sum(int num) {  
    int hundred = num / 100;  
    int ten = (num / 10) % 10;  
    int unit = num % 10;  
    return hundred*hundred*hundred +  
           ten*ten*ten +  
           unit*unit*unit;  
}  

int main() {  
    for(int n = 100; n <= 999; n++) {  
        if(calculate_sum(n) == n) {  
            printf("%d 是水仙花数\n", n);  
        }  
    }  
    return 0;  
}  

优化方向三:数学范围缩小

通过数学推导缩小遍历范围:

  • 百位h的取值为1-9,十位t和个位u为0-9。
  • 立方和h³ + t³ + u³必须等于100h + 10t + u
  • 可通过数学表达式提前计算可能的h值,但实际代码中遍历三位数的复杂度较低,优化空间有限。

进阶思考:从三位数到更广泛的问题

拓展1:寻找四位“超水仙花数”

四位数的类似问题称为“超完全数字不变数”,例如1634:
$$1^4 + 6^4 + 3^4 + 4^4 = 1634$$
代码修改思路

  • 将循环范围改为1000~9999
  • 将分解逻辑扩展为千位、百位、十位、个位。
  • 立方改为四次方。
// 四位数示例代码片段  
int calculate_4digit(int num) {  
    int thousand = num / 1000;  
    int hundred = (num / 100) % 10;  
    int ten = (num / 10) % 10;  
    int unit = num % 10;  
    return thousand*thousand*thousand*thousand +  
           hundred*hundred*hundred*hundred +  
           ten*ten*ten*ten +  
           unit*unit*unit*unit;  
}  

拓展2:其他进制的水仙花数

例如,二进制中的水仙花数可能需要重新定义条件,但这类问题复杂度较高,适合进阶挑战。


常见问题与调试技巧

问题1:代码输出为空或错误结果

可能原因

  • 循环范围错误(如n < 100n > 999)。
  • 位数分解逻辑错误(如ten的计算顺序颠倒)。
    调试方法
  • 在循环中添加printf语句,逐个验证分解后的各位数值。

问题2:立方和计算不准确

可能原因

  • 使用pow函数导致浮点精度问题。
    解决方案
  • 改用h*h*h而非pow(h,3)

结论

通过“C 练习实例13 – 水仙花数”的学习,开发者不仅能掌握基础的循环、条件判断和位数分解技巧,还能理解如何将数学问题转化为代码逻辑。本文通过分步讲解、代码示例和优化建议,帮助读者逐步构建解决问题的能力。无论是编程新手还是希望巩固基础的开发者,这一实例都是提升逻辑思维和编码实践的优秀起点。

延伸思考:尝试编写程序寻找五位数或六位数的类似数,或探索其他数学问题的编程实现,如完美数、回文数等,进一步拓展编程与数学结合的能力。

最新发布