Java数据结构与算法——数论问题

数论问题

1、完全数

1.1、什么是完全数

完全数(Perfect number)是一些特殊的自然整数。完全数等于其所有因子的和。这里所谓的因子是指所有可以整除这个数的数,而不包括该数本身。

其实谈到完全数,与之相关的两个概念便是亏数和盈数。一般来说,通过其所有真因子的和来判断一个自然数是亏数、盈数及完全数。

  • 当一个自然数的所有真因子的和小于该自然数,那么该自然数便是亏数。
  • 当一个自然数的所有真因子的和大于该自然数,那么该自然数便是盈数。
  • 当一个自然数的所有真因子的和等于该自然数,那么该自然数便是完全数。

例如,4的所有真因子包括1和2,而4>1+2,所以4是一个亏数;6的所有真因子包括1、2、3,而6=1+2+3,因此6是一个完全数;12的所有真因子包括1、2、3、4、6,而12<1+2+3+4+6,所以12是一个盈数。

下面举几个典型的完全数的例子:

  • 6=1+2+3
  • 28=1+2+4+7+14
  • 496=1+2+4+8+16+31+62+124+248
  • 8128=1+2+4+8+16+32+64+127+254+508+1016+2032+4064

完全数的特殊性质:

  1. 每一个完全数都可以表示成连续自然数的和
    每一个完全数都可以表示成连续自然数的和,这些自然数并不一定是完全数的因数。例如:
    6=1+2+3
    28=1+2+3+4+5+6+7
    496=1+2+3+4+...+29+30+31
  2. 每一个完全数都是调和数
    如果一个正整数的所有因子的调和平均是整数,那么这个正整数便是调和数。而每一个完全数都是调和数,例如:
    对于完全数6来说,1/1+1/2+1/3+1/6=2
    对于完全数28来说,1/1+1/2+1/4+1/7+1/14+1/28=2
  3. 每一个完全数都可以表示为2的一些连续正整数次幂之和
    每一个完全数都可以表示为2的一些连续正整数次幂之和例如:
    6=21+22
    28=2^2+2N3+2N4
    8128=26+27+28+29+210+211+2^12
  4. 已知的完全数都是以6或者8结尾
    已知的完全数都是以6或者8结尾,例如,6、28、496、8128、33550336等。从这里也可以看出,已知的每一个完全数都是偶数,但还没有严格证明没有奇数的完全数。
  5. 除6之外的完全数都可以表示成连续奇立方之和
    除6之外的完全数都可以表示成连续奇立方之和,例如:
    28=1A3+3^3
    496=1A3+33+53+7^3
    8128=1^3+33+53++153

1.2、计算完全数的算法

java 复制代码
/**
 * @Author: acton_zhang
 * @Date: 2026/1/9 下午2:56
 * @Version 1.0
 * 计算完全平方数
 */
public class PerfectNumberCalculator {

    /**
     * 方法1:暴力法 - 检查一个数是否为完全数
     * 时间复杂度:O(n)
     * 适用于小数字
     */
    public static boolean isPerfectNumberBruteForce(int n) {
        if (n < 2)
            return false;//完全平方数至少是6
        int sum = 1;//1是任何正整数的因子
        int limit = (int)Math.sqrt(n);//求平方根
        for (int i = 2; i <= limit; i++) {
            if (n % i == 0) {//能整除即为因子
                sum += i;
                int other = n / i;//另一个因子
                if (other  != i) {//避免重复添加因子
                    sum += other;
                }
            }
        }
        return sum == n;
    }

    /**
     * 方法2:欧几里得-欧拉定理法(只适用于偶完全数)
     * 时间复杂度:取决于梅森素数的检测
     */
    public static boolean isPerfectEuclidEuler(long n) {
        // 检查是否满足形式 2^(p-1) * (2^p - 1)
        for (int p = 2; p <= 31; p++) {  // 限制p以防止溢出
            long mersenne = (1L << p) - 1;  // 2^p - 1
            if (isPrime(mersenne)) {  // 需要是梅森素数
                long perfect = (1L << (p - 1)) * mersenne;  // 2^(p-1) * (2^p - 1)
                if (perfect == n) {
                    return true;
                }
                if (perfect > n) {
                    break;  // 由于完美数递增,可以提前结束
                }
            }
        }
        return false;
    }


    /**
     * 判断一个数是否为素数
     */
    private static boolean isPrime(long n) {
        if (n < 2)
            return false;
        if (n == 2 || n == 3)
            return true;
        if (n % 2 == 0 || n % 3 == 0)
            return true;
        long limit = (long)Math.sqrt(n) + 1;
        for (long i = 5; i <= limit; i+= 5) {
            if (n % i == 0|| n % (i + 2) == 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取一个数的所有真因子(不包括自身)
     */
    public static List<Integer> getProperDivisors(int n) {
        List<Integer> divisors = new ArrayList<>();
        if (n < 1) return divisors;

        divisors.add(1);
        int limit = (int) Math.sqrt(n);

        for (int i = 2; i <= limit; i++) {
            if (n % i == 0) {
                divisors.add(i);
                int other = n / i;
                if (other != i && other != n) {  // 避免添加自身
                    divisors.add(other);
                }
            }
        }

        divisors.sort(Integer::compareTo);
        return divisors;
    }

    /**
     * 查找指定范围内的所有完全数
     */
    public static List<Integer> findPerfectNumbersInRange(int start, int end) {
        List<Integer> perfectNumbers = new ArrayList<>();

        for (int n = Math.max(start, 2); n <= end; n++) {
            if (isPerfectNumberBruteForce(n)) {
                perfectNumbers.add(n);
            }
        }

        return perfectNumbers;
    }
}

2、亲密数

2.1、什么是亲密数

如果整数a的因子和等于整数b,整数b的因子和等于整数a,因子包括1但不包括本身,且a不等于b,则称a、b为亲密数。

例如,220和284便是一对亲密数,满足如下规则。

  • 220的各个因子之和为1+2+4+5+10+11+20+22+44+55+110=284;
    284的各个因子之和为:1+2+4+71+142=220。
  • 另外,1184和1210是一对亲密数,因为其满足如下规则:1184的各个因子之和为1+2+4+8+16+32+37+74+148+296+592=1210;
    1210的各个因子之和为:1+2+5+10+11+22+55+110+121+242+605=1184。

2.2、计算亲密数的算法

java 复制代码
/**
 * @Author: acton_zhang
 * @Date: 2026/1/9 下午10:08
 * @Version 1.0
 */
public class AmicableNumbersCalculator {


    /**
     * 方法1:计算一个数的真因子之和
     * 时间复杂度:O(√n)
     */
    public static int sumOfProperDivisors(int n) {
        if (n <= 1) return 0;

        int sum = 1;  // 1总是真因子
        int limit = (int) Math.sqrt(n);

        for (int i = 2; i <= limit; i++) {
            if (n % i == 0) {
                sum += i;
                int other = n / i;
                if (other != i && other != n) {  // 避免添加n本身
                    sum += other;
                }
            }
        }

        return sum;
    }

    /**
     * 方法2:检查两个数是否为亲密数
     */
    public static boolean areAmicable(int a, int b) {
        if (a == b) return false;  // 亲密数必须是不同的数

        int sumA = sumOfProperDivisors(a);
        int sumB = sumOfProperDivisors(b);

        return sumA == b && sumB == a;
    }

    /**
     * 方法3:查找指定范围内的所有亲密数对
     * 时间复杂度:O(n√n)
     */
    public static List<int[]> findAmicablePairsInRange(int start, int end) {
        List<int[]> pairs = new ArrayList<>();

        for (int a = Math.max(start, 2); a <= end; a++) {
            int b = sumOfProperDivisors(a);

            // 检查条件:
            // 1. b 在范围内
            // 2. b > a (避免重复)
            // 3. a 和 b 是亲密数
            if (b > a && b <= end) {
                int sumB = sumOfProperDivisors(b);
                if (sumB == a) {
                    pairs.add(new int[]{a, b});
                }
            }
        }

        return pairs;
    }

    /**
     * 方法4:优化的查找算法(缓存因子和)
     */
    public static List<int[]> findAmicablePairsOptimized(int limit) {
        List<int[]> pairs = new ArrayList<>();
        int[] divisorSums = new int[limit + 1];

        // 预计算所有数的因子和
        for (int i = 1; i <= limit; i++) {
            divisorSums[i] = sumOfProperDivisors(i);
        }

        // 查找亲密数对
        for (int a = 2; a <= limit; a++) {
            int b = divisorSums[a];

            if (b > limit) continue;  // b超出范围
            if (b <= a) continue;     // 避免重复

            if (divisorSums[b] == a) {
                pairs.add(new int[]{a, b});
            }
        }

        return pairs;
    }

    /**
     * 方法5:获取一个数的所有真因子
     */
    public static List<Integer> getProperDivisors(int n) {
        List<Integer> divisors = new ArrayList<>();
        if (n <= 1) return divisors;

        divisors.add(1);
        int limit = (int) Math.sqrt(n);

        for (int i = 2; i <= limit; i++) {
            if (n % i == 0) {
                divisors.add(i);
                int other = n / i;
                if (other != i && other != n) {
                    divisors.add(other);
                }
            }
        }

        divisors.sort(Integer::compareTo);
        return divisors;
    }

    /**
     * 方法6:检查一个数是否在已知的亲密数对中
     */
    public static boolean isInAmicablePair(int n) {
        int sum = sumOfProperDivisors(n);
        if (sum == n) return false;  // 完全数不是亲密数

        int sumOfSum = sumOfProperDivisors(sum);
        return sumOfSum == n && sum != n;
    }

    /**
     * 获取一个数的"伴侣"(如果存在)
     */
    public static Integer getAmicablePartner(int n) {
        int sum = sumOfProperDivisors(n);
        if (sum == n) return null;  // 完全数

        int sumOfSum = sumOfProperDivisors(sum);
        if (sumOfSum == n && sum != n) {
            return sum;
        }
        return null;
    }

    public static void main(String[] args) {
        System.out.println("========== 亲密数计算器 ==========\n");

        // 1. 测试已知的亲密数对
        System.out.println("1. 已知亲密数对测试:");
        int[][] knownPairs = {
                {220, 284},
                {1184, 1210},
                {2620, 2924},
                {5020, 5564},
                {6232, 6368},
                {10744, 10856},
                {12285, 14595},
                {17296, 18416}
        };

        for (int[] pair : knownPairs) {
            int a = pair[0];
            int b = pair[1];
            boolean areAmicable = areAmicable(a, b);
            List<Integer> divisorsA = getProperDivisors(a);
            List<Integer> divisorsB = getProperDivisors(b);
            int sumA = divisorsA.stream().mapToInt(Integer::intValue).sum();
            int sumB = divisorsB.stream().mapToInt(Integer::intValue).sum();

            System.out.printf("(%d, %d): %s%n", a, b, areAmicable ? "是亲密数" : "不是亲密数");
            System.out.printf("  d(%d) = %s = %d%n", a, divisorsA, sumA);
            System.out.printf("  d(%d) = %s = %d%n", b, divisorsB, sumB);
            System.out.println();
        }

        // 2. 查找范围内的亲密数
        System.out.println("2. 查找1-10000范围内的亲密数对:");
        List<int[]> pairs1 = findAmicablePairsInRange(1, 10000);
        for (int[] pair : pairs1) {
            System.out.printf("  (%d, %d)%n", pair[0], pair[1]);
        }

        // 3. 优化算法测试
        System.out.println("\n3. 优化算法测试 (1-20000):");
        List<int[]> pairs2 = findAmicablePairsOptimized(20000);
        for (int[] pair : pairs2) {
            System.out.printf("  (%d, %d)%n", pair[0], pair[1]);
        }

        // 4. 检查单个数字
        System.out.println("\n4. 检查单个数字:");
        int[] testNumbers = {220, 284, 1184, 1210, 496, 28, 12};
        for (int n : testNumbers) {
            Integer partner = getAmicablePartner(n);
            if (partner != null) {
                System.out.printf("  %d 的亲密伴侣是 %d%n", n, partner);
            } else {
                if (sumOfProperDivisors(n) == n) {
                    System.out.printf("  %d 是完全数,没有亲密伴侣%n", n);
                } else {
                    System.out.printf("  %d 没有亲密伴侣%n", n);
                }
            }
        }
    }
}
java 复制代码
========== 亲密数计算器 ==========

1. 已知亲密数对测试:
(220, 284): 是亲密数
  d(220) = [1, 2, 4, 5, 10, 11, 20, 22, 44, 55, 110] = 284
  d(284) = [1, 2, 4, 71, 142] = 220

(1184, 1210): 是亲密数
  d(1184) = [1, 2, 4, 8, 16, 32, 37, 74, 148, 296, 592] = 1210
  d(1210) = [1, 2, 5, 10, 11, 22, 55, 110, 121, 242, 605] = 1184

(2620, 2924): 是亲密数
  d(2620) = [1, 2, 4, 5, 10, 20, 131, 262, 524, 655, 1310] = 2924
  d(2924) = [1, 2, 4, 17, 34, 43, 68, 86, 172, 731, 1462] = 2620

(5020, 5564): 是亲密数
  d(5020) = [1, 2, 4, 5, 10, 20, 251, 502, 1004, 1255, 2510] = 5564
  d(5564) = [1, 2, 4, 13, 26, 52, 107, 214, 428, 1391, 2782] = 5020

(6232, 6368): 是亲密数
  d(6232) = [1, 2, 4, 8, 19, 38, 41, 76, 82, 152, 164, 328, 779, 1558, 3116] = 6368
  d(6368) = [1, 2, 4, 8, 16, 32, 199, 398, 796, 1592, 3184] = 6232

(10744, 10856): 是亲密数
  d(10744) = [1, 2, 4, 8, 17, 34, 68, 79, 136, 158, 316, 632, 1343, 2686, 5372] = 10856
  d(10856) = [1, 2, 4, 8, 23, 46, 59, 92, 118, 184, 236, 472, 1357, 2714, 5428] = 10744

(12285, 14595): 是亲密数
  d(12285) = [1, 3, 5, 7, 9, 13, 15, 21, 27, 35, 39, 45, 63, 65, 91, 105, 117, 135, 189, 195, 273, 315, 351, 455, 585, 819, 945, 1365, 1755, 2457, 4095] = 14595
  d(14595) = [1, 3, 5, 7, 15, 21, 35, 105, 139, 417, 695, 973, 2085, 2919, 4865] = 12285

(17296, 18416): 是亲密数
  d(17296) = [1, 2, 4, 8, 16, 23, 46, 47, 92, 94, 184, 188, 368, 376, 752, 1081, 2162, 4324, 8648] = 18416
  d(18416) = [1, 2, 4, 8, 16, 1151, 2302, 4604, 9208] = 17296

2. 查找1-10000范围内的亲密数对:
  (220, 284)
  (1184, 1210)
  (2620, 2924)
  (5020, 5564)
  (6232, 6368)

3. 优化算法测试 (1-20000):
  (220, 284)
  (1184, 1210)
  (2620, 2924)
  (5020, 5564)
  (6232, 6368)
  (10744, 10856)
  (12285, 14595)
  (17296, 18416)

4. 检查单个数字:
  220 的亲密伴侣是 284
  284 的亲密伴侣是 220
  1184 的亲密伴侣是 1210
  1210 的亲密伴侣是 1184
  496 是完全数,没有亲密伴侣
  28 是完全数,没有亲密伴侣
  12 没有亲密伴侣

3、水仙花数

3.1、什么是水仙花数

水仙花数是指一个n位正整数(n≥3),它的每个位上的数字的n次幂之和等于它本身。水仙花数也是一种具有特殊性质的数。

常见类型:

  • 三位水仙花数:153 = 1³ + 5³ + 3³
  • 四位水仙花数:1634 = 1⁴ + 6⁴ + 3⁴ + 4⁴
  • 阿姆斯特朗数:水仙花数的别称
  • 自幂数:水仙花数的推广

已知水仙花数:

  • 1位:1, 2, 3, 4, 5, 6, 7, 8, 9
  • 3位:153, 370, 371, 407
  • 4位:1634, 8208, 9474
  • 5位:54748, 92727, 93084
  • 6位:548834
  • 7位:1741725, 4210818, 9800817, 9926315
  • 8位:24678050, 24678051, 88593477
  • 最高位:有39位的水仙花数

3.2、计算水仙花数的算法

java 复制代码
/**
 * @Author: acton_zhang
 * @Date: 2026/1/9 下午10:31
 * @Version 1.0
 */
public class NarcissisticNumberCalculator {

    /**
     * 方法1:判断一个数是否为水仙花数
     * 时间复杂度:O(n) 其中n是数字位数
     */
    public static boolean isNarcissisticNumber(int number) {
        if (number < 0) return false;

        int original = number;
        int sum = 0;
        int digits = countDigits(number);

        while (number > 0) {
            int digit = number % 10;
            sum += Math.pow(digit, digits);
            number /= 10;
        }

        return sum == original;
    }

    /**
     * 方法2:计算数字的位数
     */
    public static int countDigits(int number) {
        if (number == 0) return 1;
        return (int) Math.log10(Math.abs(number)) + 1;
    }

    /**
     * 方法3:查找指定位数的所有水仙花数
     */
    public static List<Integer> findNarcissisticNumbersByDigits(int digits) {
        List<Integer> results = new ArrayList<>();

        int start = (int) Math.pow(10, digits - 1);
        int end = (int) Math.pow(10, digits) - 1;

        // 对于1位数,0-9也需要检查
        if (digits == 1) {
            start = 0;
        }

        for (int i = start; i <= end; i++) {
            if (isNarcissisticNumber(i)) {
                results.add(i);
            }
        }

        return results;
    }

    /**
     * 方法4:查找指定范围内的水仙花数
     */
    public static List<Integer> findNarcissisticNumbersInRange(int start, int end) {
        List<Integer> results = new ArrayList<>();

        for (int i = start; i <= end; i++) {
            if (isNarcissisticNumber(i)) {
                results.add(i);
            }
        }

        return results;
    }

    /**
     * 方法5:优化的水仙花数检查(缓存幂次)
     */
    public static boolean isNarcissisticOptimized(int number) {
        if (number < 0) return false;

        int original = number;
        int sum = 0;
        int digits = countDigits(number);

        // 缓存0-9的digits次幂
        int[] powers = new int[10];
        for (int i = 0; i <= 9; i++) {
            powers[i] = (int) Math.pow(i, digits);
        }

        while (number > 0) {
            int digit = number % 10;
            sum += powers[digit];
            number /= 10;
        }

        return sum == original;
    }

    /**
     * 方法6:获取数字的各位数字
     */
    public static List<Integer> getDigits(int number) {
        List<Integer> digits = new ArrayList<>();

        if (number == 0) {
            digits.add(0);
            return digits;
        }

        number = Math.abs(number);
        while (number > 0) {
            digits.add(0, number % 10);  // 添加到开头保持顺序
            number /= 10;
        }

        return digits;
    }

    /**
     * 方法7:验证水仙花数并显示计算过程
     */
    public static String verifyNarcissisticNumber(int number) {
        if (!isNarcissisticNumber(number)) {
            return number + " 不是水仙花数";
        }

        List<Integer> digits = getDigits(number);
        int digitCount = digits.size();
        StringBuilder process = new StringBuilder();

        process.append(number).append(" = ");
        for (int i = 0; i < digits.size(); i++) {
            int digit = digits.get(i);
            process.append(digit).append("^").append(digitCount);
            if (i < digits.size() - 1) {
                process.append(" + ");
            }
        }

        process.append(" = ");
        int sum = 0;
        for (int i = 0; i < digits.size(); i++) {
            int digit = digits.get(i);
            int power = (int) Math.pow(digit, digitCount);
            sum += power;
            process.append(power);
            if (i < digits.size() - 1) {
                process.append(" + ");
            }
        }

        process.append(" = ").append(sum);
        return process.toString();
    }

    public static void main(String[] args) {
        System.out.println("========== 水仙花数计算器 ==========\n");

        // 1. 测试已知的水仙花数
        System.out.println("1. 已知水仙花数验证:");
        int[] testNumbers = {153, 370, 371, 407, 1634, 8208, 9474, 54748, 123, 100};

        for (int num : testNumbers) {
            boolean isNarc = isNarcissisticNumber(num);
            System.out.printf("%-6d: %s", num, isNarc ? "是水仙花数" : "不是水仙花数");

            if (isNarc) {
                System.out.printf(" (验证: %s)", verifyNarcissisticNumber(num));
            }
            System.out.println();
        }

        // 2. 查找指定位数的水仙花数
        System.out.println("\n2. 查找指定位数的水仙花数:");
        for (int digits = 1; digits <= 7; digits++) {
            List<Integer> numbers = findNarcissisticNumbersByDigits(digits);
            if (!numbers.isEmpty()) {
                System.out.printf("%d 位数 (%d 个): %s%n",
                        digits, numbers.size(), numbers);
            } else {
                System.out.printf("%d 位数: 无%n", digits);
            }
        }

        // 3. 查找范围内的水仙花数
        System.out.println("\n3. 查找范围内的水仙花数:");
        int[][] ranges = {
                {1, 1000},
                {1000, 10000},
                {1, 100000}
        };

        for (int[] range : ranges) {
            List<Integer> numbers = findNarcissisticNumbersInRange(range[0], range[1]);
            System.out.printf("范围 [%d, %d]: %s%n",
                    range[0], range[1], numbers);
        }

        // 4. 优化算法测试
        System.out.println("\n4. 优化算法测试:");
        long start = System.currentTimeMillis();
        List<Integer> allNarcissistic = new ArrayList<>();
        for (int i = 1; i <= 1000000; i++) {
            if (isNarcissisticOptimized(i)) {
                allNarcissistic.add(i);
            }
        }
        long end = System.currentTimeMillis();
        System.out.printf("1-1000000 内的水仙花数 (%d 个): %s%n",
                allNarcissistic.size(), allNarcissistic);
        System.out.printf("优化算法耗时: %d ms%n", end - start);
    }
}
java 复制代码
========== 水仙花数计算器 ==========

1. 已知水仙花数验证:
153   : 是水仙花数 (验证: 153 = 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153)
370   : 是水仙花数 (验证: 370 = 3^3 + 7^3 + 0^3 = 27 + 343 + 0 = 370)
371   : 是水仙花数 (验证: 371 = 3^3 + 7^3 + 1^3 = 27 + 343 + 1 = 371)
407   : 是水仙花数 (验证: 407 = 4^3 + 0^3 + 7^3 = 64 + 0 + 343 = 407)
1634  : 是水仙花数 (验证: 1634 = 1^4 + 6^4 + 3^4 + 4^4 = 1 + 1296 + 81 + 256 = 1634)
8208  : 是水仙花数 (验证: 8208 = 8^4 + 2^4 + 0^4 + 8^4 = 4096 + 16 + 0 + 4096 = 8208)
9474  : 是水仙花数 (验证: 9474 = 9^4 + 4^4 + 7^4 + 4^4 = 6561 + 256 + 2401 + 256 = 9474)
54748 : 是水仙花数 (验证: 54748 = 5^5 + 4^5 + 7^5 + 4^5 + 8^5 = 3125 + 1024 + 16807 + 1024 + 32768 = 54748)
123   : 不是水仙花数
100   : 不是水仙花数

2. 查找指定位数的水仙花数:
1 位数 (10 个): [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2 位数: 无
3 位数 (4 个): [153, 370, 371, 407]
4 位数 (3 个): [1634, 8208, 9474]
5 位数 (3 个): [54748, 92727, 93084]
6 位数 (1 个): [548834]
7 位数 (4 个): [1741725, 4210818, 9800817, 9926315]

3. 查找范围内的水仙花数:
范围 [1, 1000]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407]
范围 [1000, 10000]: [1634, 8208, 9474]
范围 [1, 100000]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407, 1634, 8208, 9474, 54748, 92727, 93084]

4. 优化算法测试:
1-1000000 内的水仙花数 (20 个): [1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407, 1634, 8208, 9474, 54748, 92727, 93084, 548834]
优化算法耗时: 1183 ms

4、自守数

4.1、什么是自守数

自守数(Automorphic Number):一个正整数的平方的尾数等于该数本身。

数学定义:

  • 对于 n 位数 k,如果 k² 的最后 n 位等于 k,则 k 是自守数
  • 公式:k² ≡ k (mod 10ⁿ),其中 n 是 k 的位数

数学性质:

  1. 自守数总是以 5 或 6 结尾
  2. 自守数的平方以 25 或 76 结尾
  3. 有无限多个自守数
  4. 自守数序列:5, 6, 25, 76, 376, 625, 9376, 90625, 109376, 890625, ...

特殊类型:

  • 纯自守数:平方的后缀正好是原数
  • 循环自守数:更高次幂也保持自守性

4.2、计算自守数的算法

java 复制代码
import java.math.BigInteger;
import java.util.*;

/**
 * 自守数计算器
 */
public class AutomorphicNumberCalculator {
    
    /**
     * 方法1:判断一个数是否为自守数
     * 时间复杂度:O(1)
     */
    public static boolean isAutomorphicNumber(int number) {
        if (number < 0) return false;
        
        long square = (long) number * number;
        int digits = countDigits(number);
        
        // 取平方的最后digits位
        long lastDigits = square % (long) Math.pow(10, digits);
        
        return lastDigits == number;
    }
    
    /**
     * 方法2:判断大数是否为自守数
     */
    public static boolean isAutomorphicBigInteger(BigInteger number) {
        if (number.compareTo(BigInteger.ZERO) < 0) {
            return false;
        }
        
        BigInteger square = number.multiply(number);
        int digits = countDigitsBigInteger(number);
        
        // 取平方的最后digits位
        BigInteger modulus = BigInteger.TEN.pow(digits);
        BigInteger lastDigits = square.mod(modulus);
        
        return lastDigits.equals(number);
    }
    
    /**
     * 方法3:计算数字的位数
     */
    public static int countDigits(int number) {
        if (number == 0) return 1;
        return (int) Math.log10(Math.abs(number)) + 1;
    }
    
    public static int countDigitsBigInteger(BigInteger number) {
        if (number.equals(BigInteger.ZERO)) {
            return 1;
        }
        return number.toString().length();
    }
    
    /**
     * 方法4:查找指定位数的所有自守数
     */
    public static List<Integer> findAutomorphicNumbersByDigits(int digits) {
        List<Integer> results = new ArrayList<>();
        
        int start = (int) Math.pow(10, digits - 1);
        int end = (int) Math.pow(10, digits) - 1;
        
        for (int i = start; i <= end; i++) {
            if (isAutomorphicNumber(i)) {
                results.add(i);
            }
        }
        
        return results;
    }
    
    /**
     * 方法5:查找指定范围内的自守数
     */
    public static List<Integer> findAutomorphicNumbersInRange(int start, int end) {
        List<Integer> results = new ArrayList<>();
        
        for (int i = start; i <= end; i++) {
            if (isAutomorphicNumber(i)) {
                results.add(i);
            }
        }
        
        return results;
    }
    
    /**
     * 方法6:优化的自守数检查
     */
    public static boolean isAutomorphicOptimized(int number) {
        if (number < 0) return false;
        
        // 快速检查:自守数必须以5或6结尾
        int lastDigit = number % 10;
        if (!(lastDigit == 5 || lastDigit == 6)) {
            return false;
        }
        
        return isAutomorphicNumber(number);
    }
    
    /**
     * 方法7:生成自守数的平方验证字符串
     */
    public static String verifyAutomorphicNumber(int number) {
        if (!isAutomorphicNumber(number)) {
            return number + " 不是自守数";
        }
        
        long square = (long) number * number;
        int digits = countDigits(number);
        
        StringBuilder sb = new StringBuilder();
        sb.append(number).append("² = ").append(square).append("\n");
        sb.append("平方的后").append(digits).append("位: ");
        
        long lastDigits = square % (long) Math.pow(10, digits);
        sb.append(lastDigits).append(" = ").append(number);
        
        return sb.toString();
    }
    
    /**
     * 方法8:查找下一个自守数
     */
    public static int findNextAutomorphicNumber(int start) {
        for (int i = start + 1; i <= Integer.MAX_VALUE; i++) {
            if (isAutomorphicNumber(i)) {
                return i;
            }
        }
        return -1; // 未找到
    }
    
    /**
     * 方法9:查找前n个自守数
     */
    public static List<Integer> findFirstNAutomorphicNumbers(int n) {
        List<Integer> results = new ArrayList<>();
        int count = 0;
        int current = 0;
        
        while (count < n) {
            if (isAutomorphicNumber(current)) {
                results.add(current);
                count++;
            }
            current++;
        }
        
        return results;
    }
    
    public static void main(String[] args) {
        System.out.println("========== 自守数计算器 ==========\n");
        
        // 1. 测试已知的自守数
        System.out.println("1. 已知自守数验证:");
        int[] testNumbers = {5, 6, 25, 76, 376, 625, 9376, 90625, 109376, 12, 20};
        
        for (int num : testNumbers) {
            boolean isAuto = isAutomorphicNumber(num);
            System.out.printf("%-8d: %s", num, isAuto ? "是自守数" : "不是自守数");
            
            if (isAuto) {
                System.out.printf(" (验证: %s)", verifyAutomorphicNumber(num));
            }
            System.out.println();
        }
        
        // 2. 查找指定位数的自守数
        System.out.println("\n2. 查找指定位数的自守数:");
        for (int digits = 1; digits <= 7; digits++) {
            List<Integer> numbers = findAutomorphicNumbersByDigits(digits);
            if (!numbers.isEmpty()) {
                System.out.printf("%d 位数 (%d 个): %s%n", 
                    digits, numbers.size(), numbers);
            } else {
                System.out.printf("%d 位数: 无%n", digits);
            }
        }
        
        // 3. 查找范围内的自守数
        System.out.println("\n3. 查找范围内的自守数:");
        int[][] ranges = {
            {1, 100},
            {100, 1000},
            {1000, 10000},
            {1, 1000000}
        };
        
        for (int[] range : ranges) {
            List<Integer> numbers = findAutomorphicNumbersInRange(range[0], range[1]);
            System.out.printf("范围 [%d, %d]: %s%n", 
                range[0], range[1], numbers);
        }
        
        // 4. 优化算法测试
        System.out.println("\n4. 优化算法测试:");
        long start = System.currentTimeMillis();
        List<Integer> allAutomorphic = new ArrayList<>();
        for (int i = 1; i <= 1000000; i++) {
            if (isAutomorphicOptimized(i)) {
                allAutomorphic.add(i);
            }
        }
        long end = System.currentTimeMillis();
        System.out.printf("1-1000000 内的自守数 (%d 个): %s%n", 
            allAutomorphic.size(), allAutomorphic);
        System.out.printf("优化算法耗时: %d ms%n", end - start);
        
        // 5. 查找前N个自守数
        System.out.println("\n5. 前20个自守数:");
        List<Integer> first20 = findFirstNAutomorphicNumbers(20);
        for (int i = 0; i < first20.size(); i++) {
            int num = first20.get(i);
            long square = (long) num * num;
            System.out.printf("%2d. %-8d (平方: %d)%n", i+1, num, square);
        }
    }
}

5、最大公约数

5.1、什么是最大公约数

最大公约数和最小公倍数应用广泛。如果有一个自然数a能被自然数b整除,则称a为b的倍数,b为a的约数。几个自然数公有的约数,叫做这几个自然数的公约数。公约数中最大的一个,一般就称为这几个自然数的最大公约数(greatest common divisor,缩写为gcd)。

例如,在自然数4、8、12中,1、2和4是这几个自然数的公约数,而4则是最大公约数。

5.2、辗转相除法

欧几里得的辗转相除算法是计算两个自然数最大公约数的传统算法,对于多个自然数可以执行多次辗转相除法来得到最大公约数。辗转相除法的执行过程如下:

  1. 对于已知两自然数m、n,假设m>n;
  2. 计算m除以n,将得到的余数记为r:
  3. 如果=0,则n为求得的最大公约数,否则执行下面一步:
  4. 将n的值保存到m中,将r的值保存到n中,重复执行步骤(2)和(3),直到r=0,便得到最大公约数。
java 复制代码
import java.math.BigInteger;
import java.util.*;

/**
 * 辗转相除法计算最大公约数
 */
public class EuclideanAlgorithm {
    
    /**
     * 方法1:递归实现辗转相除法
     * 时间复杂度:O(log(min(a, b)))
     */
    public static int gcdRecursive(int a, int b) {
        // 处理负数
        a = Math.abs(a);
        b = Math.abs(b);
        
        if (b == 0) {
            return a;
        }
        return gcdRecursive(b, a % b);
    }
    
    /**
     * 方法2:迭代实现辗转相除法
     */
    public static int gcdIterative(int a, int b) {
        a = Math.abs(a);
        b = Math.abs(b);
        
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }
    
    /**
     * 方法3:更安全的迭代实现(处理边界情况)
     */
    public static int gcdSafe(int a, int b) {
        if (a == 0 && b == 0) {
            return 0;  // 定义gcd(0,0)=0
        }
        
        a = Math.abs(a);
        b = Math.abs(b);
        
        // 处理特殊情况
        if (a == 0) return b;
        if (b == 0) return a;
        
        // 确保a >= b
        if (a < b) {
            int temp = a;
            a = b;
            b = temp;
        }
        
        while (b != 0) {
            int remainder = a % b;
            a = b;
            b = remainder;
        }
        
        return a;
    }
    
    /**
     * 方法4:计算最小公倍数(LCM)
     * 公式:lcm(a, b) = |a * b| / gcd(a, b)
     */
    public static int lcm(int a, int b) {
        if (a == 0 || b == 0) {
            return 0;
        }
        
        int gcd = gcdSafe(a, b);
        return Math.abs(a / gcd * b);  // 先除后乘避免溢出
    }
    
    /**
     * 方法5:计算多个数的最大公约数
     */
    public static int gcdMultiple(int... numbers) {
        if (numbers == null || numbers.length == 0) {
            return 0;
        }
        
        int result = numbers[0];
        for (int i = 1; i < numbers.length; i++) {
            result = gcdSafe(result, numbers[i]);
            if (result == 1) {
                break;  // 提前终止
            }
        }
        return result;
    }
    
    /**
     * 方法6:计算多个数的最小公倍数
     */
    public static int lcmMultiple(int... numbers) {
        if (numbers == null || numbers.length == 0) {
            return 0;
        }
        
        int result = numbers[0];
        for (int i = 1; i < numbers.length; i++) {
            result = lcm(result, numbers[i]);
        }
        return result;
    }
    
    /**
     * 方法7:扩展欧几里得算法
     * 返回数组 [gcd, x, y] 满足 ax + by = gcd
     */
    public static int[] extendedGcd(int a, int b) {
        if (b == 0) {
            return new int[]{a, 1, 0};
        }
        
        int[] result = extendedGcd(b, a % b);
        int gcd = result[0];
        int x1 = result[1];
        int y1 = result[2];
        
        int x = y1;
        int y = x1 - (a / b) * y1;
        
        return new int[]{gcd, x, y};
    }
    
    /**
     * 方法8:二进制GCD算法(更高效)
     */
    public static int binaryGcd(int a, int b) {
        a = Math.abs(a);
        b = Math.abs(b);
        
        if (a == 0) return b;
        if (b == 0) return a;
        
        // 提取公因子2
        int shift = 0;
        while (((a | b) & 1) == 0) {  // 都是偶数
            a >>= 1;
            b >>= 1;
            shift++;
        }
        
        while ((a & 1) == 0) {  // a是偶数
            a >>= 1;
        }
        
        // 现在a是奇数
        do {
            while ((b & 1) == 0) {  // b是偶数
                b >>= 1;
            }
            
            if (a > b) {
                int temp = a;
                a = b;
                b = temp;
            }
            
            b = b - a;
        } while (b != 0);
        
        return a << shift;  // 恢复因子2
    }
    
    /**
     * 方法9:使用BigInteger计算大数GCD
     */
    public static BigInteger gcdBigInteger(BigInteger a, BigInteger b) {
        return a.gcd(b);
    }
    
    /**
     * 方法10:验证贝祖等式 ax + by = gcd(a, b)
     */
    public static boolean verifyBezout(int a, int b) {
        int[] result = extendedGcd(a, b);
        int gcd = result[0];
        int x = result[1];
        int y = result[2];
        
        int leftSide = a * x + b * y;
        return leftSide == gcd;
    }
    
    public static void main(String[] args) {
        System.out.println("========== 辗转相除法计算最大公约数 ==========\n");
        
        // 1. 测试基本GCD计算
        System.out.println("1. 基本GCD计算:");
        int[][] testPairs = {
            {48, 18},
            {56, 98},
            {101, 103},
            {0, 5},
            {5, 0},
            {0, 0},
            {-48, 18},
            {48, -18},
            {1071, 462},
            {17, 19}
        };
        
        for (int[] pair : testPairs) {
            int a = pair[0];
            int b = pair[1];
            
            int gcdRec = gcdRecursive(a, b);
            int gcdIter = gcdIterative(a, b);
            int gcdBin = binaryGcd(a, b);
            
            System.out.printf("gcd(%3d, %3d) = %2d (递归) = %2d (迭代) = %2d (二进制)%n", 
                a, b, gcdRec, gcdIter, gcdBin);
        }
        
        // 2. 扩展欧几里得算法
        System.out.println("\n2. 扩展欧几里得算法:");
        int[][] extendedPairs = {
            {48, 18},
            {56, 98},
            {17, 19}
        };
        
        for (int[] pair : extendedPairs) {
            int a = pair[0];
            int b = pair[1];
            int[] result = extendedGcd(a, b);
            int gcd = result[0];
            int x = result[1];
            int y = result[2];
            
            System.out.printf("%d*%d + %d*%d = %d", a, x, b, y, gcd);
            System.out.printf(" 验证: %d*%d + %d*%d = %d %s%n", 
                a, x, b, y, a*x + b*y, 
                (a*x + b*y == gcd) ? "✓" : "✗");
        }
        
        // 3. 最小公倍数计算
        System.out.println("\n3. 最小公倍数计算:");
        for (int[] pair : testPairs) {
            int a = pair[0];
            int b = pair[1];
            int lcm = lcm(a, b);
            int gcd = gcdSafe(a, b);
            
            if (a != 0 && b != 0) {
                System.out.printf("lcm(%3d, %3d) = %6d, 验证: %d*%d/gcd = %d%n", 
                    a, b, lcm, a, b, Math.abs(a*b)/gcd);
            }
        }
        
        // 4. 多个数的GCD和LCM
        System.out.println("\n4. 多个数的GCD和LCM:");
        int[] numbers1 = {12, 18, 24};
        int[] numbers2 = {15, 25, 35, 45};
        
        int gcdMulti1 = gcdMultiple(numbers1);
        int lcmMulti1 = lcmMultiple(numbers1);
        System.out.printf("GCD(%s) = %d, LCM(%s) = %d%n", 
            Arrays.toString(numbers1), gcdMulti1, 
            Arrays.toString(numbers1), lcmMulti1);
        
        int gcdMulti2 = gcdMultiple(numbers2);
        int lcmMulti2 = lcmMultiple(numbers2);
        System.out.printf("GCD(%s) = %d, LCM(%s) = %d%n", 
            Arrays.toString(numbers2), gcdMulti2, 
            Arrays.toString(numbers2), lcmMulti2);
        
        // 5. 大数计算
        System.out.println("\n5. 大数计算:");
        BigInteger bigA = new BigInteger("12345678901234567890");
        BigInteger bigB = new BigInteger("98765432109876543210");
        BigInteger bigGcd = gcdBigInteger(bigA, bigB);
        System.out.printf("GCD(%s,%n    %s) =%n    %s%n", 
            bigA, bigB, bigGcd);
        
        // 6. 性能比较
        System.out.println("\n6. 算法性能比较:");
        compareAlgorithmPerformance();
    }
    
    private static void compareAlgorithmPerformance() {
        int a = 123456789;
        int b = 987654321;
        int iterations = 1000000;
        
        // 递归算法
        long start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            gcdRecursive(a, b);
        }
        long recursiveTime = System.nanoTime() - start;
        
        // 迭代算法
        start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            gcdIterative(a, b);
        }
        long iterativeTime = System.nanoTime() - start;
        
        // 二进制算法
        start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            binaryGcd(a, b);
        }
        long binaryTime = System.nanoTime() - start;
        
        System.out.printf("递归算法: %.3f ms%n", recursiveTime / 1_000_000.0);
        System.out.printf("迭代算法: %.3f ms%n", iterativeTime / 1_000_000.0);
        System.out.printf("二进制算法: %.3f ms%n", binaryTime / 1_000_000.0);
    }
}

5.3、Stein算法

Stein算法(也称为二进制GCD算法):通过位移运算高效计算最大公约数。

算法特点:

  • 不使用除法,只使用位移、加减和与运算
  • 特别适合大整数和硬件实现
  • 时间复杂度:O(log(min(a, b)))
java 复制代码
import java.math.BigInteger;
import java.util.*;

/**
 * Stein算法(二进制GCD算法)
 */
public class SteinAlgorithm {
    
    /**
     * 方法1:基础Stein算法实现
     * 时间复杂度:O(log(min(a, b)))
     */
    public static int steinGCD(int a, int b) {
        if (a == 0) return Math.abs(b);
        if (b == 0) return Math.abs(a);
        
        // 处理负数
        a = Math.abs(a);
        b = Math.abs(b);
        
        // 提取公因子2
        int shift = 0;
        while (((a | b) & 1) == 0) {  // a和b都是偶数
            a >>= 1;
            b >>= 1;
            shift++;
        }
        
        // 现在a和b至少有一个是奇数
        while ((a & 1) == 0) {  // a是偶数
            a >>= 1;
        }
        
        // 使用Stein算法
        do {
            while ((b & 1) == 0) {  // b是偶数
                b >>= 1;
            }
            
            // 现在a和b都是奇数
            if (a > b) {
                int temp = a;
                a = b;
                b = temp;
            }
            
            b = b - a;
        } while (b != 0);
        
        return a << shift;  // 恢复因子2
    }
    
    /**
     * 方法2:优化版Stein算法
     */
    public static int steinGCDOptimized(int a, int b) {
        if (a == 0) return Math.abs(b);
        if (b == 0) return Math.abs(a);
        
        a = Math.abs(a);
        b = Math.abs(b);
        
        // 使用Java内置函数计算尾随0的个数
        int shift = Integer.numberOfTrailingZeros(a | b);
        a >>= Integer.numberOfTrailingZeros(a);
        
        do {
            b >>= Integer.numberOfTrailingZeros(b);
            if (a > b) {
                int temp = a;
                a = b;
                b = temp;
            }
            b -= a;
        } while (b != 0);
        
        return a << shift;
    }
    
    /**
     * 方法3:迭代实现Stein算法
     */
    public static int steinIterative(int a, int b) {
        a = Math.abs(a);
        b = Math.abs(b);
        
        if (a == 0) return b;
        if (b == 0) return a;
        
        int shift = 0;
        // 移除公因子2
        while (((a | b) & 1) == 0) {
            shift++;
            a >>= 1;
            b >>= 1;
        }
        
        // 保证a是奇数
        while ((a & 1) == 0) {
            a >>= 1;
        }
        
        // 主循环
        while (b != 0) {
            // 保证b是奇数
            while ((b & 1) == 0) {
                b >>= 1;
            }
            
            // 使用减法
            if (a > b) {
                int temp = a;
                a = b;
                b = temp;
            }
            
            b -= a;
        }
        
        return a << shift;
    }
    
    /**
     * 方法4:使用尾递归实现
     */
    public static int steinRecursive(int a, int b) {
        a = Math.abs(a);
        b = Math.abs(b);
        
        if (a == 0) return b;
        if (b == 0) return a;
        if (a == b) return a;
        
        // 如果a和b都是偶数
        if ((a & 1) == 0 && (b & 1) == 0) {
            return steinRecursive(a >> 1, b >> 1) << 1;
        }
        
        // 如果a是偶数,b是奇数
        if ((a & 1) == 0) {
            return steinRecursive(a >> 1, b);
        }
        
        // 如果a是奇数,b是偶数
        if ((b & 1) == 0) {
            return steinRecursive(a, b >> 1);
        }
        
        // 如果a和b都是奇数
        if (a > b) {
            return steinRecursive((a - b) >> 1, b);
        } else {
            return steinRecursive(a, (b - a) >> 1);
        }
    }
    
    /**
     * 方法5:大数Stein算法
     */
    public static BigInteger steinBigInteger(BigInteger a, BigInteger b) {
        a = a.abs();
        b = b.abs();
        
        if (a.equals(BigInteger.ZERO)) return b;
        if (b.equals(BigInteger.ZERO)) return a;
        
        int shift = 0;
        
        // 提取公因子2
        while (a.and(BigInteger.ONE).equals(BigInteger.ZERO) && 
               b.and(BigInteger.ONE).equals(BigInteger.ZERO)) {
            a = a.shiftRight(1);
            b = b.shiftRight(1);
            shift++;
        }
        
        // 主循环
        while (!b.equals(BigInteger.ZERO)) {
            // 移除b的因子2
            while (b.and(BigInteger.ONE).equals(BigInteger.ZERO)) {
                b = b.shiftRight(1);
            }
            
            // 保证a <= b
            if (a.compareTo(b) > 0) {
                BigInteger temp = a;
                a = b;
                b = temp;
            }
            
            b = b.subtract(a);
        }
        
        return a.shiftLeft(shift);
    }
    
    /**
     * 方法6:比较Stein算法和欧几里得算法
     */
    public static void compareAlgorithms(int a, int b) {
        long start, end;
        
        // Stein算法
        start = System.nanoTime();
        int steinResult = steinGCDOptimized(a, b);
        end = System.nanoTime();
        long steinTime = end - start;
        
        // 欧几里得算法
        start = System.nanoTime();
        int euclidResult = euclideanGCD(a, b);
        end = System.nanoTime();
        long euclidTime = end - start;
        
        // 二进制算法
        start = System.nanoTime();
        int binaryResult = binaryGCD(a, b);
        end = System.nanoTime();
        long binaryTime = end - start;
        
        System.out.printf("gcd(%d, %d) = %d%n", a, b, steinResult);
        System.out.printf("Stein算法:    %.3f µs%n", steinTime / 1000.0);
        System.out.printf("欧几里得算法: %.3f µs%n", euclidTime / 1000.0);
        System.out.printf("二进制算法:   %.3f µs%n", binaryTime / 1000.0);
        
        // 验证结果
        boolean correct = (steinResult == euclidResult && euclidResult == binaryResult);
        System.out.println("结果验证: " + (correct ? "✓" : "✗"));
    }
    
    /**
     * 欧几里得算法
     */
    private static int euclideanGCD(int a, int b) {
        a = Math.abs(a);
        b = Math.abs(b);
        
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }
    
    /**
     * 二进制GCD算法
     */
    private static int binaryGCD(int a, int b) {
        a = Math.abs(a);
        b = Math.abs(b);
        
        if (a == 0) return b;
        if (b == 0) return a;
        
        int shift = 0;
        while (((a | b) & 1) == 0) {
            a >>= 1;
            b >>= 1;
            shift++;
        }
        
        while ((a & 1) == 0) {
            a >>= 1;
        }
        
        do {
            while ((b & 1) == 0) {
                b >>= 1;
            }
            
            if (a > b) {
                int temp = a;
                a = b;
                b = temp;
            }
            b -= a;
        } while (b != 0);
        
        return a << shift;
    }
    
    public static void main(String[] args) {
        System.out.println("========== Stein算法(二进制GCD) ==========\n");
        
        // 1. 测试基本功能
        System.out.println("1. 基本功能测试:");
        int[][] testPairs = {
            {48, 18},
            {56, 98},
            {1071, 462},
            {0, 5},
            {5, 0},
            {0, 0},
            {-48, 18},
            {48, -18},
            {1, 1},
            {17, 19}
        };
        
        for (int[] pair : testPairs) {
            int a = pair[0];
            int b = pair[1];
            
            int stein = steinGCD(a, b);
            int steinOpt = steinGCDOptimized(a, b);
            int steinIter = steinIterative(a, b);
            int steinRec = steinRecursive(a, b);
            int euclid = euclideanGCD(a, b);
            
            System.out.printf("gcd(%3d, %3d) = %2d (Stein) = %2d (优化) = %2d (迭代) = %2d (递归) = %2d (欧几里得)%n", 
                a, b, stein, steinOpt, steinIter, steinRec, euclid);
            
            // 验证一致性
            boolean consistent = (stein == steinOpt && steinOpt == steinIter && 
                                steinIter == steinRec && steinRec == euclid);
            if (!consistent) {
                System.out.println("  警告:结果不一致!");
            }
        }
        
        // 2. 算法性能比较
        System.out.println("\n2. 算法性能比较:");
        int[][] performanceTests = {
            {123456789, 987654321},
            {999999937, 999999938},  // 相邻大数
            {1 << 20, 1 << 19},      // 2的幂
            {2147483647, 2147483646} // 接近int最大值
        };
        
        for (int[] pair : performanceTests) {
            System.out.printf("\n测试: gcd(%,d, %,d)%n", pair[0], pair[1]);
            compareAlgorithms(pair[0], pair[1]);
        }
        
        // 3. 大数测试
        System.out.println("\n3. 大数测试:");
        BigInteger bigA = new BigInteger("12345678901234567890");
        BigInteger bigB = new BigInteger("98765432109876543210");
        
        BigInteger steinBig = steinBigInteger(bigA, bigB);
        BigInteger euclidBig = bigA.gcd(bigB);
        
        System.out.printf("Stein算法: gcd(%s,%n    %s) =%n    %s%n", 
            bigA, bigB, steinBig);
        System.out.printf("Java内置: gcd(%s,%n    %s) =%n    %s%n", 
            bigA, bigB, euclidBig);
        System.out.println("结果一致: " + steinBig.equals(euclidBig) ? "✓" : "✗");
        
        // 4. 批量测试
        System.out.println("\n4. 批量测试 (1000个随机数对):");
        batchTest(1000);
        
        // 5. 特殊情况测试
        System.out.println("\n5. 特殊情况测试:");
        specialCasesTest();
    }
    
    private static void batchTest(int count) {
        Random random = new Random();
        int correct = 0;
        long totalSteinTime = 0;
        long totalEuclidTime = 0;
        
        for (int i = 0; i < count; i++) {
            int a = random.nextInt(1000000) + 1;
            int b = random.nextInt(1000000) + 1;
            
            long start = System.nanoTime();
            int steinResult = steinGCDOptimized(a, b);
            long steinTime = System.nanoTime() - start;
            
            start = System.nanoTime();
            int euclidResult = euclideanGCD(a, b);
            long euclidTime = System.nanoTime() - start;
            
            if (steinResult == euclidResult) {
                correct++;
            }
            
            totalSteinTime += steinTime;
            totalEuclidTime += euclidTime;
        }
        
        System.out.printf("正确率: %.1f%%%n", (correct * 100.0 / count));
        System.out.printf("平均时间 - Stein: %.3f µs, 欧几里得: %.3f µs%n", 
            totalSteinTime / (count * 1000.0), 
            totalEuclidTime / (count * 1000.0));
        System.out.printf("性能比: %.2fx%n", 
            (double) totalEuclidTime / totalSteinTime);
    }
    
    private static void specialCasesTest() {
        System.out.println("测试边界情况:");
        
        // 测试2的幂
        System.out.println("\n1. 2的幂:");
        for (int i = 1; i <= 10; i++) {
            int a = 1 << i;  // 2^i
            int b = 1 << (i + 1);  // 2^(i+1)
            int result = steinGCDOptimized(a, b);
            System.out.printf("gcd(2^%d, 2^%d) = %d%n", i, i+1, result);
        }
        
        // 测试费波那契数
        System.out.println("\n2. 费波那契数:");
        int fib1 = 1, fib2 = 1;
        for (int i = 3; i <= 10; i++) {
            int fib3 = fib1 + fib2;
            int result = steinGCDOptimized(fib2, fib3);
            System.out.printf("gcd(F%d, F%d) = %d%n", i-1, i, result);
            fib1 = fib2;
            fib2 = fib3;
        }
        
        // 测试质数
        System.out.println("\n3. 质数:");
        int[] primes = {2, 3, 5, 7, 11, 13, 17, 19};
        for (int i = 0; i < primes.length - 1; i++) {
            for (int j = i + 1; j < primes.length; j++) {
                int result = steinGCDOptimized(primes[i], primes[j]);
                System.out.printf("gcd(%d, %d) = %d%n", primes[i], primes[j], result);
            }
        }
    }
}

6、最小公倍数

6.1、什么是最小公倍数

最小公倍数(Least Common Multiple):两个或多个整数公有的倍数中最小的一个。

数学性质:

  1. 公式:lcm(a, b) = |a × b| / gcd(a, b)
  2. 对于多个数:lcm(a, b, c) = lcm(lcm(a, b), c)
  3. 如果a和b互质,则lcm(a, b) = a × b
  4. lcm(0, n) = 0(定义)

6.2、计算最小公倍数的算法

java 复制代码
import java.math.BigInteger;
import java.util.*;

/**
 * 最小公倍数(LCM)计算器
 */
public class LCMCalculator {
    
    /**
     * 方法1:通过GCD计算LCM
     * 公式:lcm(a, b) = |a * b| / gcd(a, b)
     */
    public static int lcmByGCD(int a, int b) {
        if (a == 0 || b == 0) {
            return 0;  // 定义lcm(0, n) = 0
        }
        
        // 先除后乘避免溢出
        int gcd = gcd(a, b);
        return Math.abs(a / gcd * b);
    }
    
    /**
     * 计算最大公约数(欧几里得算法)
     */
    private static int gcd(int a, int b) {
        a = Math.abs(a);
        b = Math.abs(b);
        
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }
    
    /**
     * 方法2:质因数分解法
     */
    public static int lcmByFactorization(int a, int b) {
        if (a == 0 || b == 0) {
            return 0;
        }
        
        a = Math.abs(a);
        b = Math.abs(b);
        
        // 质因数分解
        Map<Integer, Integer> factorsA = primeFactorization(a);
        Map<Integer, Integer> factorsB = primeFactorization(b);
        
        // 合并因数,取最高次幂
        Set<Integer> allPrimes = new HashSet<>(factorsA.keySet());
        allPrimes.addAll(factorsB.keySet());
        
        int lcm = 1;
        for (int prime : allPrimes) {
            int expA = factorsA.getOrDefault(prime, 0);
            int expB = factorsB.getOrDefault(prime, 0);
            int maxExp = Math.max(expA, expB);
            lcm *= (int) Math.pow(prime, maxExp);
        }
        
        return lcm;
    }
    
    /**
     * 质因数分解
     */
    private static Map<Integer, Integer> primeFactorization(int n) {
        Map<Integer, Integer> factors = new HashMap<>();
        
        // 处理因子2
        int count = 0;
        while (n % 2 == 0) {
            n /= 2;
            count++;
        }
        if (count > 0) {
            factors.put(2, count);
        }
        
        // 处理奇数因子
        for (int i = 3; i * i <= n; i += 2) {
            count = 0;
            while (n % i == 0) {
                n /= i;
                count++;
            }
            if (count > 0) {
                factors.put(i, count);
            }
        }
        
        // 处理剩余的质因数
        if (n > 1) {
            factors.put(n, 1);
        }
        
        return factors;
    }
    
    /**
     * 方法3:逐倍查找法
     */
    public static int lcmByBruteForce(int a, int b) {
        if (a == 0 || b == 0) {
            return 0;
        }
        
        a = Math.abs(a);
        b = Math.abs(b);
        
        int max = Math.max(a, b);
        int min = Math.min(a, b);
        int lcm = max;
        
        while (lcm % min != 0) {
            lcm += max;
            
            // 防止溢出
            if (lcm < 0) {
                throw new ArithmeticException("溢出: LCM太大");
            }
        }
        
        return lcm;
    }
    
    /**
     * 方法4:计算多个数的LCM
     */
    public static int lcmMultiple(int... numbers) {
        if (numbers == null || numbers.length == 0) {
            return 0;
        }
        
        int result = numbers[0];
        for (int i = 1; i < numbers.length; i++) {
            result = lcmByGCD(result, numbers[i]);
        }
        return result;
    }
    
    /**
     * 方法5:大数LCM计算
     */
    public static BigInteger lcmBigInteger(BigInteger a, BigInteger b) {
        if (a.signum() == 0 || b.signum() == 0) {
            return BigInteger.ZERO;
        }
        
        BigInteger gcd = a.gcd(b);
        return a.divide(gcd).multiply(b).abs();
    }
    
    /**
     * 方法6:优化的大数计算(避免中间溢出)
     */
    public static BigInteger lcmBigIntegerSafe(BigInteger a, BigInteger b) {
        if (a.signum() == 0 || b.signum() == 0) {
            return BigInteger.ZERO;
        }
        
        BigInteger gcd = a.gcd(b);
        return a.divide(gcd).multiply(b).abs();
    }
    
    /**
     * 方法7:计算分数的最小公分母
     */
    public static int commonDenominator(int[] numerators, int[] denominators) {
        if (numerators.length != denominators.length) {
            throw new IllegalArgumentException("分子分母数组长度不一致");
        }
        
        int lcmDenominators = 1;
        for (int denominator : denominators) {
            if (denominator == 0) {
                throw new ArithmeticException("分母不能为0");
            }
            lcmDenominators = lcmByGCD(lcmDenominators, Math.abs(denominator));
        }
        
        return lcmDenominators;
    }
    
    /**
     * 方法8:验证LCM性质
     */
    public static boolean verifyLCM(int a, int b, int lcm) {
        if (a == 0 || b == 0) {
            return lcm == 0;
        }
        
        // 性质1: lcm能被a和b整除
        if (lcm % Math.abs(a) != 0 || lcm % Math.abs(b) != 0) {
            return false;
        }
        
        // 性质2: 任何a和b的公倍数都能被lcm整除
        int testMultiple = Math.abs(a) * Math.abs(b);
        return testMultiple % lcm == 0;
    }
    
    public static void main(String[] args) {
        System.out.println("========== 最小公倍数(LCM)计算器 ==========\n");
        
        // 1. 测试基本LCM计算
        System.out.println("1. 基本LCM计算:");
        int[][] testPairs = {
            {12, 18},
            {15, 20},
            {7, 13},
            {0, 5},
            {5, 0},
            {8, 12},
            {21, 28},
            {100, 120},
            {1, 1},
            {1, 100}
        };
        
        for (int[] pair : testPairs) {
            int a = pair[0];
            int b = pair[1];
            
            int lcmGCD = lcmByGCD(a, b);
            int lcmFactor = lcmByFactorization(a, b);
            int lcmBrute = lcmByBruteForce(a, b);
            
            System.out.printf("lcm(%3d, %3d) = %5d (GCD法) = %5d (分解法) = %5d (逐倍法)", 
                a, b, lcmGCD, lcmFactor, lcmBrute);
            
            boolean correct = (lcmGCD == lcmFactor && lcmFactor == lcmBrute);
            System.out.println(correct ? " ✓" : " ✗");
        }
        
        // 2. 多个数的LCM
        System.out.println("\n2. 多个数的LCM:");
        int[][] numberSets = {
            {12, 18, 24},
            {4, 6, 8, 12},
            {3, 7, 11},
            {1, 2, 3, 4, 5}
        };
        
        for (int[] numbers : numberSets) {
            int lcm = lcmMultiple(numbers);
            System.out.printf("lcm(%s) = %d%n", 
                Arrays.toString(numbers), lcm);
            
            // 验证
            for (int num : numbers) {
                if (num != 0 && lcm % Math.abs(num) != 0) {
                    System.out.println("  验证失败: " + num + " 不能整除 " + lcm);
                }
            }
        }
        
        // 3. 大数计算
        System.out.println("\n3. 大数LCM计算:");
        BigInteger bigA = new BigInteger("12345678901234567890");
        BigInteger bigB = new BigInteger("98765432109876543210");
        BigInteger bigLcm = lcmBigInteger(bigA, bigB);
        
        System.out.printf("lcm(%s,%n    %s) =%n    %s%n", 
            bigA, bigB, bigLcm);
        
        // 验证
        BigInteger remainderA = bigLcm.remainder(bigA);
        BigInteger remainderB = bigLcm.remainder(bigB);
        System.out.printf("验证: %s %% %s = %s, %s %% %s = %s%n", 
            bigLcm, bigA, remainderA, bigLcm, bigB, remainderB);
        System.out.println("验证结果: " + 
            (remainderA.equals(BigInteger.ZERO) && remainderB.equals(BigInteger.ZERO) ? "✓" : "✗"));
        
        // 4. 分数运算
        System.out.println("\n4. 分数通分:");
        int[] numerators = {1, 2, 3};
        int[] denominators = {2, 3, 4};
        
        int commonDenom = commonDenominator(numerators, denominators);
        System.out.printf("分数: 1/2, 2/3, 3/4%n");
        System.out.printf("最小公分母: %d%n", commonDenom);
        System.out.printf("通分后: %d/%d, %d/%d, %d/%d%n", 
            numerators[0] * (commonDenom / denominators[0]), commonDenom,
            numerators[1] * (commonDenom / denominators[1]), commonDenom,
            numerators[2] * (commonDenom / denominators[2]), commonDenom);
        
        // 5. 性能比较
        System.out.println("\n5. 算法性能比较:");
        compareAlgorithmPerformance();
    }
    
    private static void compareAlgorithmPerformance() {
        int a = 12345;
        int b = 67890;
        int iterations = 100000;
        
        System.out.printf("测试: lcm(%,d, %,d), 迭代次数: %,d%n", a, b, iterations);
        
        // GCD法
        long start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            lcmByGCD(a, b);
        }
        long timeGCD = System.nanoTime() - start;
        
        // 分解法
        start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            lcmByFactorization(a, b);
        }
        long timeFactor = System.nanoTime() - start;
        
        // 逐倍法
        start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            lcmByBruteForce(a, b);
        }
        long timeBrute = System.nanoTime() - start;
        
        System.out.printf("GCD法:     %.3f ms%n", timeGCD / 1_000_000.0);
        System.out.printf("分解法:    %.3f ms%n", timeFactor / 1_000_000.0);
        System.out.printf("逐倍法:    %.3f ms%n", timeBrute / 1_000_000.0);
        
        // 验证结果一致性
        int resultGCD = lcmByGCD(a, b);
        int resultFactor = lcmByFactorization(a, b);
        int resultBrute = lcmByBruteForce(a, b);
        
        boolean consistent = (resultGCD == resultFactor && resultFactor == resultBrute);
        System.out.println("结果一致性: " + (consistent ? "✓" : "✗"));
    }
}

7、素数

7.1、什么是素数

素数又称为质数,指在一个大于1的自然数中,除了1和此整数自身外,无法被其他自然数整除的数。比1大,但不是素数的称为合数。0和1既不是素数也不是合数。

素数的分布没有很明显的规律,仍然有很多伟大的数学家对其进行研究。例如,最早的欧几里得、17世纪法国的费尔马、法国的梅森等。

谈到素数,有著名的算术基本定理:任何一个大于1的正整数,可以唯一表示成有限个

素数的乘积。

围绕着素数,数学家提出了各种猜想,内容如下。

  • 黎曼猜想:黎曼研究发现,素数分布的绝大部分猜想都取决于黎曼zeta函数的零点位置。黎曼在此基础上,猜想那些非平凡零点都落在复平面中实部为1/2的直线上。
  • 孪生素数猜想:对于素数n,如果n+2同样为素数,则称n和n+2为孪生素数。到底有没有无穷多个孪生素数呢?这是一个至今仍无法解决的问题。
  • 哥德巴赫猜想:哥德巴赫通过大量的数据猜测,所有不小于6的偶数,都可以表示为两个奇素数之和。后人将其称为"1+1"。并且,对于每个不小于9的奇数,都可以表示为三个奇素数之和。哥德巴赫猜想是数学中的璀璨明珠,至今仍无实质性进展。

近年来,素数在密码学上又发现了新的应用。基于大的素数质因数分解的复杂性,从而构造出广泛应用的公钥密码。这是现代密码学的基础,是密码学领域激动人心的进步。

7.2、计算素数的算法

java 复制代码
	/**
     * 欧拉筛法(线性筛法)
     */
    public static List<Integer> eulerSieve(int limit) {
        if (limit < 2) return new ArrayList<>();
        
        List<Integer> primes = new ArrayList<>();
        boolean[] isPrime = new boolean[limit + 1];
        Arrays.fill(isPrime, true);
        isPrime[0] = isPrime[1] = false;
        
        for (int i = 2; i <= limit; i++) {
            if (isPrime[i]) {
                primes.add(i);
            }
            
            for (int j = 0; j < primes.size() && i * primes.get(j) <= limit; j++) {
                isPrime[i * primes.get(j)] = false;
                if (i % primes.get(j) == 0) {
                    break;
                }
            }
        }
        
        return primes;
    }
相关推荐
Miketutu9 小时前
Flutter - 布局
开发语言·javascript·ecmascript
这就是佬们吗9 小时前
Windows 的 CMD 网络环境:解决终端无法联网与更新的终极指南
java·windows·git·python·spring·maven
栈与堆9 小时前
数据结构篇(1) - 5000字细嗦什么是数组!!!
java·开发语言·数据结构·python·算法·leetcode·柔性数组
yuanmenghao9 小时前
自动驾驶中间件iceoryx - 同步与通知机制(一)
开发语言·网络·驱动开发·中间件·自动驾驶
企鹅会滑雪9 小时前
【无标题】
开发语言·python
幻云20109 小时前
Next.js 之道:从全栈思维到架构实战
开发语言·javascript·架构
阿豪学编程9 小时前
【Linux】线程同步和线程互斥
linux·开发语言
寻星探路9 小时前
【深度长文】深入理解网络原理:TCP/IP 协议栈核心实战与性能调优
java·网络·人工智能·python·网络协议·tcp/ip·ai
AI_56789 小时前
基于职业发展的Python与Java深度对比分析
java·人工智能·python·信息可视化