质数判定,质因数分解,两种质数筛:埃氏筛、线性筛(欧拉筛)

质数判定

试除法,根据定义,枚举 [ 2 , n − 1 ] [2,n-1] [2,n1] 中所有整数,看是否有能整除 n n n 的数 。

事实上,我们没有必要枚举出所有整数

a × b = n a\times b=n a×b=n,我们就说 a a a b b b n n n 的因数,所以因数都是成对的,并且对称分布在 n \sqrt n n 两边,我们只需要找各对因数中较小的一个,而较小的因数一定小于等于 n \sqrt n n

bool isPrime(int n)
{
    if (n < 2) return false;
    for (int i = 2; i <= n / i; ++i)
    {
        if (n % i == 0) return false;
    }
    return true;
}

注意:判断条件 i <= n / i 是最优的写法。不建议写成 i <= sqrt(n),因为 sqrt() 求根号的速度比较慢。也不建议写成 i * i <= n 因为 i * i 容易导致 int 溢出。

时间复杂度: O ( n ) O(\sqrt n) O(n )

质因数分解

每个合数都可以写成几个质数相乘的形式,其中每个质数都是这个合数的因数,把一个合数用质因数相乘的形式表示出来,叫做分解质因数。如 30 = 2 × 3 × 5 30=2\times3\times5 30=2×3×5 10080 = 2 5 × 3 2 × 5 × 7 10080 = 2^5\times3^2\times5\times7 10080=25×32×5×7。分解质因数只针对合数。

求一个数分解质因数,要从最小的质数除起,一直除到结果为质数为止。分解质因数的算式叫短除法,和除法的性质相似,还可以用来求多个数的公因式。

短除法:

img

质因数分解代码:vector<pair<int, int>> 用于存放各质因数和对应的次数。

vector<pair<int, int>> divide(int x)
{
    vector<pair<int, int>> res;
    for (int i = 2; i <= x / i; ++i)
    {
        if (x % i == 0)
        {
            int s = 0;
            while (x % i == 0)
            {
                x /= i;
                ++s;
            }
            res.push_back({ i, s });
        }
    }
    if (x > 1) res.push_back({ x, 1 });
    return res;
}

这样写之所以正确,是基于一个基本原理:一个数除 1 1 1 以外的最小的因数一定是质数。

反证法很容易证明这一点:假设一个数 x x x 1 1 1 以外的最小的因数 y y y 不是质数,那么 y y y 有除 1 1 1 和它本身的因数 z z z z z z 一定也是 x x x 的因数,而 z < y z < y z<y,所以 y y y 不是 x x x 除1以外的最小的因数,与假设矛盾,原命题得证。

以上代码就是先找到这个数的第一个因数(以下所指的因数都不包括 1 1 1),它一定是质数,把它除干净之后得到一个新的数,新的数的最小的因数一定也是质数,而且比之前的大。最后如果 x x x 不能再分解,即 i i i 枚举到 x \sqrt x x 正好可以判断出 x x x 是个质数或者是 1,最后通过 x > 1 判断它也是一个质因数。

筛质数

找出 [ 1 , n ] [1,n] [1,n] 中所有的质数。

埃氏筛

[ 2 , n ] [2,n] [2,n] 全部列出来,依次划掉 2,3,4,5……n 的倍数。

对于任意一个 p 而言,如果它没有被划掉,那么说明它不是前面2~p-1的数的倍数,所以它一定是质数。

图解:

img

一开始可以确定 2 2 2 是质数,然后把 2 2 2 的倍数全部划掉; 3 3 3 没有被划掉,所以可以确定 3 3 3 是质数,然后把 3 3 3 的倍数全部划掉; 4 4 4 已经被 2 2 2 划掉了,它的倍数一定也是 2 2 2 的倍数,没必要再划了,跳过; 5 5 5 没有被划掉,可以确定 5 5 5 是质数,然后把 5 5 5 的倍数全部划掉……

4 4 4 这样的,合数的倍数不用再划,因为合数一定是前面某个数的倍数,那么它的倍数一定也是前面某个数的倍数。

这里还可以优化一个细节:比如我们在划 3 3 3 的倍数的时候,可以不从 3 × 2 = 6 3\times2=6 3×2=6 开始划,因为 6 6 6 已经被 2 2 2 划过了,而可以从 3 × 3 = 9 3\times3=9 3×3=9 开始划。同理,在划 5 5 5 的倍数的时候,应该从 5 × 5 = 25 5\times5=25 5×5=25 开始划,因为 5 × 2 5\times2 5×2 5 × 3 5\times3 5×3 5 × 4 5\times4 5×4 都被 2 2 2 3 3 3 划过了。

代码:

开两个数组,vector<int> primes用于存放质数,vector<bool> isPrime用于记录这些数是否被划掉,初始化所有数为 true,被划掉就标记成 false

void getPrimes(vector<int>& primes, vector<bool>& isPrime, int n)
{
    for (int i = 2; i <= n; ++i)
    {
        if (isPrime[i])
        {
            primes.push_back(i);
            for (long long j = (long long)i * i; j <= n; j += i)
            {
                isPrime[j] = false;
            }
        }
    }
}

防止 i * i 导致 int 溢出的写法:

void getPrimes(vector<int>& primes, vector<bool>& isPrime, int n)
{
    for (int i = 2; i <= n; ++i)
    {
        if (isPrime[i])
        {
            primes.push_back(i);
            for (int j = i; j <= n / i; ++j)
            {
                isPrime[j * i] = false;
            }
        }
    }
}

时间复杂度: O ( n log ⁡ log ⁡ n ) O(n\log\log n) O(nloglogn);这里的循环次数应该是 n 2 + n 3 + n 5 + ⋯ \frac{n}{2}+\frac{n}{3}+\frac{n}{5}+\cdots 2n+3n+5n+,把 n n n 提出来就是 n ( 1 2 + 1 3 + 1 5 + ⋯   ) n(\frac12+\frac13+\frac15+\cdots) n(21+31+51+),后面的一堆就是质数的倒数之和,它其实相当于 O ( log ⁡ log ⁡ n ) O(\log\log n) O(loglogn) ,所以总体的时间复杂度记为 O ( n log ⁡ log ⁡ n ) O(n\log\log n) O(nloglogn)

线性筛(欧拉筛)

为了提高效率,我们可以保证每个合数只被划掉一次,具体来说,是被它的最小质因数划掉。

我们知道,任何合数 x x x 都能分解质因数,因为合数的定义是不仅能被 1 1 1 和自己整除,还能被其它整数整除,这其它整数逐级分解最终还是质数。而且其质因数中,一定有小于等于 x \sqrt x x 的质因数,这是线性筛能筛掉所有合数的基本保证。

具体方法就是划掉所有 x 乘小于等于 x 的最小质因数的质数所得到的数 x = 2,3,4,5,6……,下面我们结合代码来看:

void getPrimes(vector<int>& primes, vector<bool>& isPrime, int n)
{
    for (int i = 2; i <= n; ++i)
    {
        if (isPrime[i])
            primes.push_back(i);
        for (auto p : primes)
        {
            if (p * i > n) break;
            isPrime[p * i] = false;
            if (i % p == 0) break;
        }
    }
}

第 11 行,枚举到 i % p == 0break,因为我们是从小到大枚举当前得到的质数,所以

  • i % p == 0 时,p 一定是 i 的最小质因数,同时 p 一定是 p * i 的最小质因数,划掉 p * i

  • i % p != 0 时,pi 的最小质因数还要小,所以 p 也一定是 p * i 的最小质因数,划掉 p * i

此两种状态都保证了,p * i 是被自己的最小质因数筛掉。

如果 i % p == 0 时不 break,而是继续向后枚举质数,那么 p 就不是 p * i 的最小质因数了,因为此时 p > i的最小质因数i的最小质因数 也是 p * i 的质因数。而 i的最小质因数 不能保证是 p * i 的最小质因数,因为它不是从最小的质数开始枚举的。

对于一个合数 x x x,假设 p p p 是它的最小质因数,当 i 枚举到 x / p x/p x/p 的时候, x x x 就被筛掉了,所以 x x x 永远比 i i i 快一步,如果 i i i 没被筛掉,可以确定 i i i 是质数。

时间复杂度: O ( n ) O(n) O(n)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

世真

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

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

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

打赏作者

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

抵扣说明:

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

余额充值