C语言算法:数论问题

5 篇文章 0 订阅

目录

完全数

水仙花数

素数

回文素数

平方回文数

分解质因数

最大公约数

最小公倍数

Stein 算法求最大公约数

小结


数论是研究整数及其性质的数学学科。在数论中,研究的对象主要是整数、质数、素数、约数、同余、模运算等基本概念和性质。数论的问题简单而又充满了智慧,本文将探讨一些基本的数论问题和算法。

完全数

完全数指的是一个正整数,它的所有因子之和等于它本身,而因子不包括该数本身。一个数的因子就是所有可以整除这个数的数,而不包括该数本身。可以通过完全数的定义来编写查找完全数的算法。计算一个数的所有因子之和,判断与其是否相等。

目前并没有找到一种高效的方法来直接计算任意大数是否为完全数。已知的完全数非常稀少,而且它们的规律并不明确。随着数字的增大,它的因子数量也随之增多,计算过程会变得更加耗时,越往后查找完全数的速度越慢。为避免造成“程序失去响应”的误会,本案例会输出当前正在判断的数字,并且覆盖已经输出的非完全数。

#include <stdio.h>
#include <math.h>

// 判断一个数是否完全数,如果是,返回因子个数,否则返回 0
int perfectNumber(int number, int *factors) {
    int i = 0, factor_sum = 0;

    // 从 1 开始,逐个判断是否是 number 的因子,不包括 number 自身
    for (int num = 1; num < number; num++) {
        // 如果 num 是 number 的因子,则累加,并将 num 记录到 factors 数组中
        if (number % num == 0) {
            factor_sum += num;
            // 如果因子之和已经大于 number,说明 number 不是完全数,直接返回
            if (factor_sum > number) return 0;
            factors[i++] = num;

        }
    }

    // 如果因子之和等于 number,则 number 是完全数,返回因子个数
    return factor_sum == number ? i : 0;
}

int main() {
    int n;
    printf("请输入一个正整数 n:");
    scanf("%d", &n);

    printf("小于 %d 的完全数有:\n", n);
    for (int i = 1; i < n; i++) {
        // 覆盖输出当前数字,用于输出进度
        printf("\r%d", i);
        int factors[300];
        int count = perfectNumber(i, factors);
        if (count > 0) {
            printf(" = ");
            // 用 + 号连接完全数的因子
            for (int j = 0; j < count; j++) {
                printf("%d", factors[j]);
                if (j < count - 1) {
                    printf(" + ");
                }
            }
            printf("\n");
        }
    }

    // 输出 n 的宽度个空格,覆盖最后一个非完数的输出
    printf("\r");
    for (int i = 0; i < log10(n) + 1; i++) {
        printf(" ");
    }

    return 0;
}

水仙花数

水仙花数(也称为自恋数、自幂数)指的是一个 n 位的正整数(n>=3),它的每个数字的 n 次幂之和等于它本身。

#include <stdio.h>
#include <math.h>

// 判断一个数是否水仙花数
int isNarcissusNumber(int num) {
    int sum = 0, temp = num, digit;
    // 求 num 的位数 n
    int n = (int) log10(num) + 1;
    // 计算 num 的各位数字的 n 次方和
    while (temp) {
        // 取出 temp 的个位数字
        digit = temp % 10;
        // 累加 digit 的 n 次方
        sum += pow(digit, n);
        // 去掉 temp 的个位数字
        temp /= 10;
    }
    return sum == num;
}

int main() {
    int n, num;
    printf("请输入一个正整数 n:");
    scanf("%d", &n);

    printf("小于 %d 的水仙花数有:\n", n);
    for (num = 100; num < n; num++) {
        if (isNarcissusNumber(num)) {
            printf("%d ", num);
        }
    }
    printf("\n");

    return 0;
}

素数

素数又称质数,指在一个大于 1 的自然数中,除了 1 和此自然数自身外,无法被其它自然数整除的数。素数的分布没有明显的规律,一般根据素数的定义来分析数值不大的数是否为素数。

#include <stdio.h>

// 判断一个数是否素数
int isPrimeNumber(int num) {
    // 素数定义为大于 1 的自然数,所以小于等于 1 的数都不是素数
    if (num <= 1) {
        return 0;
    }
    // 从 2 开始,逐个判断是否能整除 num,不包括 num 自身
    for (int i = 2; i < num; i++) {
        if (num % i == 0) {
            // 如果能被整除,则 num 不是素数
            return 0;
        }
    }
    // 如果不能被整除,则 num 是素数
    return 1;
}

int main() {
    int l = 0, n, m, num, width;
    printf("请输入一个正整数 n:");
    scanf("%d", &n);
    printf("每行输出多少个数:");
    scanf("%d", &m);

    printf("小于 %d 的素数有:\n", n);
    // 输出素数宽度为 n 的宽度+1
    width = (int)log10(n) + 2;
    for (num = 2; num < n; num++) {
        if (isPrimeNumber(num)) {
            printf("%*d", width, num);
            if (++l % m == 0) {
                printf("\n");
            }
        }
    }
    printf("\n");

    return 0;
}

回文素数

回文素数指的是一个既是回文数又是素数的正整数。回文数是指正着读和倒着读都一样的数字。判断回文素数也从其定义出发,复用上一案例的素数判断方法,本案例增加方法判断是否为回文数。

#include <stdio.h>

// 判断一个数是否回文数
int isPalindrome(int num) {
    int n = num, reverse = 0;
    while (n > 0) {
        // 将 reverse 左移一位,然后加上 n 的个位数字
        reverse = reverse * 10 + n % 10;
        // 去掉 n 的个位数字
        n /= 10;
    }
    return reverse == num;
}

// 判断一个数是否素数,见上一案例
int isPrimeNumber(int num)

int main() {
    int n, num;
    printf("请输入一个正整数 n:");
    scanf("%d", &n);

    printf("小于 %d 的回文素数有:\n", n);
    for (num = 2; num < n; num++) {
        if (isPalindrome(num) && isPrimeNumber(num)) {
            printf("%d ", num);
        }
    }
    printf("\n");

    return 0;
}

平方回文数

平方回文数是一个回文数,这个回文数可以表示成某个自然数的平方的形式。

#include <stdio.h>

// 判断一个数是否回文数,见上一案例
int isPalindrome(int num)

int main() {
    int n;
    printf("请输入一个正整数 n:");
    scanf("%d", &n);

    printf("小于 %d 的平方回文数有:\n", n);
    for (int i = 1; i * i < n; i++) {
        if (isPalindrome(i * i)) {
            printf("%d = %d * %d\n", i * i, i, i);
        }
    }

    return 0;
}

分解质因数

分解质因数是指将一个合数分解成若干个质数的乘积的过程。合数是指在大于 1 的整数中除了能被 1 和本身整除外,还能被其他数整除的数。质数即前面提到的素数,是只能被 1 和自身整除的正整数。分解质因数的过程可以用试除法实现,具体步骤如下:

1. 从 2 开始,依次尝试将待分解的数除以 2,如果可以整除,则将 2 作为一个质因子,继续将商分解成质因数;

2. 如果商不可以被 2 整除,则尝试将其除以 3,如果可以整除,则将 3 作为一个质因子,继续将商分解成质因数;

3. 依此类推,直到商为 1,分解过程结束。

#include <stdio.h>

// 分解质因数
void factorize(int num) {
    printf("%d = ", num);
    int i = 2, n = num;
    while (n > 1) {
        // 如果能被整除,则 i 是 n 的质因子
        if (n % i == 0) {
            printf("%d", i);
            // 去掉 n 的因子 i
            n /= i;
            if (n > 1) {
                // 如果 n 还大于 1,则说明还有其他因子,所以输出乘号
                printf(" × ");
            }
        } else {
            i++;
        }
    }
    if (i == num) {
        // 如果 i 等于 num,说明 num 是质数,覆盖上面的输出
        printf("\r%d 是质数\n", num);
    } else {
        printf("\n");
    }
}

int main() {
    int n;
    printf("请输入一个正整数:");
    scanf("%d", &n);

    factorize(n);
    return 0;
}

最大公约数

最大公约数是指两个或多个整数的公共因数中最大的一个。可以用辗转相除法来求两个数的最大公约数。已知两自然数 m、n,辗转相除法求最大公约数执行过程如下:

1. 计算 m 除以 n 的余数 r;

2. 如果 r==0,则 n 为求得的最大公约数,否则执行下一步;

3. 将 n 的值保存到 m 中,将 r 的值保存到 n 中,重复执行步骤 1 和 2,直到 r==0 得到最大公约数。

由于是取模运算,m 和 n 谁大谁小没有关系,如果 m<n,执行一遍上述步骤后,m 和 n 的值就交换过来了,然后正式进入辗转相除的流程。

#include <stdio.h>

// 求最大公约数
int gcd(int m, int n) {
    int r;
    do {
        r = m % n;
        if (r == 0) return n;
        m = n;
        n = r;
    } while (1);
}

// 求最小公倍数
int lcm(int m, int n) {
    return m * n / gcd(m, n);
}

int main() {
    int a, b;
    printf("请输入两个正整数:");
    scanf("%d %d", &a, &b);

    printf("它们的最大公约数是:%d\n", gcd(a, b));
    printf("它们的最小公倍数是:%d\n", lcm(a, b));
    return 0;
}

最小公倍数

最小公倍数是两个数共有的倍数中最小的一个。计算两个数的最小公倍数的方法比较简单,先计算它们的最大公约数,然后用两个数的乘积除以它们的最大公约数,便可得到两数的最小公倍数。相关代码见上一案例。

Stein 算法求最大公约数

还可以使用Stein算法来求两个数的最大公约数,该算法相比前面的辗转相除法,使用位移和加减运算,且递归深度更小,效率更高。该算法如下:

#include <stdio.h>
#define ll long long

// Stein 算法求最大公约数
ll gcdStein(ll a, ll b) {
    // m 是两个数中较大的那个,n 是较小的那个
    ll m, n;
    if (a > b) {
        m = a;
        n = b;
    } else {
        m = b;
        n = a;
    }
    printf("m = %lld, n = %lld\n", m, n);

    // 如果其中一个数为0,另一个数就是它们的最大公约数
    if (n == 0) return m;

    // 如果两个数都是偶数,则公约数也是2的倍数
    while ((m & 1) == 0 && (n & 1) == 0) {
        return 2 * gcdStein(m >>= 1, n >>= 1);
    }

    // 如果 m 是偶数,n 是奇数,m/2 递归调用
    while ((m & 1) == 0) {
        return gcdStein(m >>= 1, n);
    }

    // 如果 n 是偶数,m 是奇数,n/2 递归调用
    while ((n & 1) == 0) {
        return gcdStein(m, n >>= 1);
    }

    // 两个奇数的最大公约数是 (m + n) / 2 和 (m - n) / 2 的最大公约数
    return gcdStein((m + n) >> 1, (m - n) >> 1);
}

int main() {
    ll a, b;
    printf("请输入两个正整数:");
    scanf("%lld %lld", &a, &b);

    ll result = gcdStein(a, b);
    printf("它们的最大公约数是:%lld\n", result);
    return 0;
}

小结

当探索数论问题时,我们遇到了许多有趣的概念和算法。我们了解了完全数、水仙花数、回文素数、平方回文数等等。我们还学习了一些常见的数论算法,如分解质因数、最大公约数、最小公倍数等。

数论作为数学的一个分支,不仅令人着迷,而且具有广泛的应用。它在密码学、编码理论、计算机科学等领域起着重要的作用。通过深入研究数论问题,我们能够培养出抽象思维、逻辑推理和问题解决能力。

希望本文对读者有所启发,增加对数论问题的兴趣和理解。数论世界广阔而精彩,还有许多有待探索的领域和问题。无论是学术研究还是日常生活中的应用,数论都是一个充满挑战和奇妙的领域,值得我们深入探索。祝愿大家在数论的世界中找到乐趣,并不断拓展自己的知识和思维。

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

创意程序员

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

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

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

打赏作者

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

抵扣说明:

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

余额充值