1. 引言

完美数是一种特殊的正整数,它等于其所有真因子(不包括自身)之和。这类数字在整数领域极为罕见——人类历史上仅发现少量完美数,前四个分别是6、28、496和8128。本文将深入探讨完美数的概念,并介绍三种不同的判断方法。

2. 理解完美数

通过具体例子理解完美数:

  • 数字6的真因子为1、2、3,其和1+2+3=6
  • 数字28的真因子为1、2、4、7、14,其和1+2+4+7+14=28

✅ 当真因子之和等于自身时,该数即为完美数。下面介绍三种判断方法。

3. 暴力法

最直接的方法是遍历所有可能的真因子并求和:

public static boolean isPerfectBruteForce(int number) {
    int sum = 0;
    for (int i = 1; i <= number / 2; i++) {
        if (number % i == 0) {
            sum += i;
        }
    }
    return sum == number;
}

⚠️ 关键点:

  • 遍历范围到number/2即可,因为最大真因子不超过number/2
  • 时间复杂度为O(n),适合小数字但效率较低

4. 基于流的方法

利用Java Stream优化计算:

public static boolean isPerfectStream(int number) {
    int sum = IntStream.rangeClosed(2, (int) Math.sqrt(number))
      .filter(test -> number % test == 0)
      .reduce(1, (s, test) -> s + test + (number / test));
    return sum == number;
}

✅ 优化原理:

  • 因子成对出现(如a和number/a),只需遍历到√number
  • 同时累加因子及其配对值(如test和number/test)
  • 时间复杂度降至O(√n),显著提升效率

5. 欧拉-欧几里得定理

基于数学定理的高效方法:

若$2^{p-1} \times (2^p - 1)$是素数(其中p为素数),则$(2^p - 1) \times 2^{p-1}$是完美数

基础实现:

public static boolean isPerfectEuclidEuler(int number) {
    int p = 2;
    int perfectNumber = (int) (Math.pow(2, p - 1) * (Math.pow(2, p) - 1));
    while (perfectNumber <= number) {
        if (perfectNumber == number) {
            return true;
        }
        p++;
        perfectNumber = (int) (Math.pow(2, p - 1) * (Math.pow(2, p) - 1));
    }
    return false;
}

❌ 性能陷阱:Math.pow()计算效率低下,需优化为位运算:

public static boolean isPerfectEuclidEulerUsingShift(int number) {
    int p = 2;
    int perfectNumber = (2 << (p - 1)) * ((2 << p) - 1);
    while (perfectNumber <= number) {
        if (perfectNumber == number) {
            return true;
        }
        p++;
        perfectNumber = (2 << (p - 1)) * ((2 << p) - 1);
    }
    return false;
}

✅ 优化效果:

  • (2 << p)替代Math.pow(2, p)
  • 时间复杂度降至O(log n),适合大数计算

6. 分析与比较

三种方法性能对比(JMH基准测试,测试数33550336):

方法 吞吐量(ops/s) 误差 特点
暴力法 55.070 ±2.674 简单但低效
流方法 96,114.246 ±3,666.451 平衡简洁与效率
欧拉-欧几里得(优化版) 99,191,865.954 ±5,924,410.475 极致性能

📊 结论:

  1. 暴力法:仅适合小数字,大数时性能急剧下降
  2. 流方法:代码简洁,中等规模数据表现良好
  3. 欧拉-欧几里得(优化版):性能碾压其他方法,尤其适合大数场景

7. 总结

完美数判断的三种方法各有适用场景:

  • 小规模数据:暴力法足够
  • 中等规模:流方法平衡可读性与效率
  • 大规模数据:优化后的欧拉-欧几里得定理是最佳选择

实际开发中,建议根据数据规模选择合适算法。源码可在GitHub获取。


原始标题:How to Check Number Perfection