素数问题

安稳与你 提交于 2020-01-25 01:25:46

如何判断一个数是否为素数

暴力方法

判断一个数n是否为素数,只需在(1,n)看是否存在它的因子

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

优化版1

定理:如果n是合数,在\((1,\sqrt{n}]\)必定存在它的因子
证明:设\(n=ab\)
假设a,b都小于\(\sqrt{n}\),那么\(ab<\sqrt{n}\sqrt{n}=n\),矛盾
假设a,b都大于\(\sqrt{n}\),那么\(ab>\sqrt{n}\sqrt{n}=n\),矛盾

因此我们可以得知,只需在\((1,\sqrt{n}]\)中看是否存在它的因子即可,再者可先判断n是否为偶数(2除外),不是偶数的话,只需在\((1,\sqrt{n}]\)中的奇数看是否存在它的因子即可

bool IsPrime(int n)
{
    if(n < 2)
        return false;
    else if(n == 2)
        return true;
    else if(!(n&1))//n是偶数
        return false;
    else
    {
        int num = sqrt(n);
        for(int i = 3;i <= num;i += 2)
            if(n % i == 0)
                return false;
        return true;
    }
}

优化版2

定理:任何一个合数=若干个素数的积
证明:
合数的因子只有素数,那么定理成立,如果因子有合数,合数又可以分解,不断分解,最终分解出素数,由于素数是分解出的,所以肯定可以整除最初的合数

这样的话,我们只需在\((1,\sqrt{n})\)中的素数看是否存在它的因子即可,表明如果知道\((1,\sqrt{n}]\)所有质数,判断n是否为质数将变得很快

假设Primes[i]是递增的素数序列: 2, 3, 5, 7, ...,更准确地说primes[i]序列包含\((1,\sqrt{n}]\)范围内的所有素数

bool IsPrime(int primes[], int n)
{
    if(n < 2)
        return false;
    const int limit = sqrt(n);
    for(int i = 0;primes[i] <= limit;++i)
        if(n%primes[i] == 0)
            return false;
    return true;
}

这样的话,我们只需找出\((1,\sqrt{n}]\)所有素数

构造素数列

原始方法

我们在构造的时候完全可以利用已经被构造的素数序列,假设我们已经得到素数序列: \(p_1, p_2, .. p_n\)
现在要判断\(p_{n+1}\)是否是素数, 则需要\((1, \sqrt{p_{n+1}}]\)范围内的所有素数序列,\(p_{n+1}\)为素数,\(p_{n+1} \% p_i == 0,(pi < \sqrt{p_{n+1}})\)

构造num个素数

void MakePrimes(int Primes[],const int num)//Primes指向的内存足够大
{
    if(num == 1)
    {
        Primes[0] = 2;
        return;
    }
    else if(num == 2)
    {
        Primes[0] = 2;
        Primes[1] = 3;
        return;
    }
    //下面作为已知的素数列
    Primes[0] = 2;
    Primes[1] = 3;
    int sub = 2;
    for(int current = 5;sub < num;++current)
    {
        bool flag = true;
        const int max = sqrt(current);
        for(int i = 0;Primes[i] <= max;++i)
            if(current % Primes[i] == 0)
            {
                flag = false;
                break;
            }
        if(flag)
            Primes[sub++] = current;
    }
}

不超过max的所有质数

int MakePrimes(int Primes[],const int max)//Primes指向的内存足够大
{
    if(max < 2)
        return 0;
    else if(max == 2)
    {
        Primes[0] = 2;
        return 1;
    }
    else if(max < 5)
    {
        Primes[0] = 2;
        Primes[1] = 3;
        return 2;
    }
    //下面作为已知的素数列
    Primes[0] = 2;
    Primes[1] = 3;
    int sub = 2;
    for(int current = 5;current < max;++current)
    {
        bool flag = true;
        const int max = sqrt(current);
        for(int i = 0;Primes[i] <= max;++i)
            if(current % Primes[i] == 0)
            {
                flag = false;
                break;
            }
        if(flag)
            Primes[sub++] = current;
    }
    return sub;
}

sieve of Eratosthenes(埃拉托斯特尼筛法)

简介

给出要筛数值的范围max,找出max以内的素数\(p_1,p_2,p_3,..,p_k\)
具体做法是:

  • 列出2以后的所有序列:
    2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ...
  • 标出序列中的第一个质数,也就是2(加粗),序列变成:
    2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ...
  • 将剩下序列中,划掉2的倍数,序列变成:
    3 5 7 9 11 13 15 17 19 21 23 25 ...
  • 如果现在最大的素数\(>\sqrt{max}\),那么剩下的序列中所有的数都是质数,否则用下一个素数,也就是3筛,把3留下,把3的倍数剔除掉。再判断,再循环...

原理:保证每次得到的素数不会是前面数的倍数,也就是它的因子只有1和它本身

步骤解疑

  • 为什么要判断最大素数>\(\sqrt{max}\)
    因为如果数字i > \(\sqrt{max}\)且i是合数,则在\([2,\sqrt{i}]\)必有它的因子,它的因子会将i标记,而i的最大值是max,所以判断最大素数>\(\sqrt{max}\),这样会加快筛选
  • 为什么不将合数的倍数标记
    因为合数的倍数也是它的因子的倍数,它的因子肯定有素数,在前面已经将倍数标记了

代码

int MakePrimes(int Primes[],const int max)
{
    if(max < 2)
        return 0;
    bool *list = new bool[max + 1];//bool[i]表示数字i是否为素数
    memset(list,true,sizeof(bool)*(max + 1));
    list[0] = list[1] = false;
    list[2] = true;
    const int limits = sqrt(max);
    for(int i = 2;i <= limits;i++)
        if(list[i])
            for(int j = i + i;j <= max;j += i)
                list[j] = false;
    int sub = 0;
    for(int i = 2;i <= max;i++)
        if(list[i])
            Primes[sub++] = i;
    delete [] list;
    return sub;
}

Eratosthenes改进

将上面从最大素数后面开始划掉素数的倍数改成从最大素数的平方后面开始划掉素数的倍数,也就是int j = i + i改成int j = i×i

假设i是目前已知的最大素数,那么[2,i * i]之间的合数的因子必定在[2,i]之间,故已经被i前面的质数标记了,所以无需划掉

int MakePrimes(int Primes[],const int max)
{
    if(max < 2)
        return 0;
    bool *list = new bool[max + 1];//bool[i]表示数字i是否为素数
    memset(list,true,sizeof(bool)*(max + 1));
    list[0] = list[1] = false;
    list[2] = true;
    const int limits = sqrt(max);
    for(int i = 2;i <= limits;i++)
        if(list[i])
            for(int j = i * i;j <= max;j += i)
                list[j] = false;
    int sub = 0;
    for(int i = 2;i <= max;i++)
        if(list[i])
            Primes[sub++] = i;
    delete [] list;
    return sub;
}

Sieve of Euler(欧拉筛法)

简介

欧拉筛法(Sieve of Euler)是埃拉托斯特尼筛法的一种改进。有人称其为快速线性筛法。回顾经典的埃拉托斯特尼筛法,它可能对同一个素数筛去多次,故时间复杂度为O(n log logn)。如果用某种方法使得每个合数只被筛去一次就变成是线性的了。不妨规定每个合数只用其最小的一个质因数去筛,这便是欧拉筛了,时间复杂度 O(n).

代码

int MakePrimes(int Primes[],const int max)
{
    if(max < 2)
        return 0;
    bool *list = new bool[max + 1];//bool[i]表示数字i是否为素数
    memset(list,true,sizeof(bool)*(max + 1));
    int sub = 0;
    for(int i = 2;i < n;++i)
    {
        if(list[i])
            Primes[sub++] = i;
        for(int j = 0;j < sub && i*Primes[j] < n;++j)
        {
            list[i*Primes[j]] = false;
            if(!(i % Primes[j])//if(i%Primes[j]==0)
                break;
        }
    }
    delete [] list;
    return sub;
}

代码解读与证明

每个合数n都可以化成这样的形式:\(n=a_1^{k_1}a_2^{k_2}...a_n^{k_n}\)其中\(a_1,a_2,..,a_n\)是素数,且\(a_1<a_2<..<a_n\)。而欧拉筛选正是利用合数最小的素数因子去划掉它本身。
证明需要从两方面去证,1.每个合数都会被划掉(正确性);2.每个合数只会被划掉一次,也就是达到O(n)(复杂度)。

for(int j = 0;j < sub && i*Primes[j] < n;++j)
{
    list[i*Primes[j]] = false;
    if(i % Primes[j] == 0)
        break;
}

在代码中,对于每个属于(1,n]的数i,都会划掉它×小于等于它本身的素数,保证了正确性。

  1. 如果i本身是素数,那么数n=i×Primes[j]只有这种分解形式,且Primes[j]≤i(先只看<,不看=),那么这用这种划法是唯一的(不重复)。
  2. 如果i本身是合数,先看i不是Primes[j]倍数的时候,那么它的所有素数(Primes[j])倍数都会被划掉,而这些素数恰好是这些数n=i×Primes[j]的最小因子。
  3. if(i%prime[j]==0)break;这行代码神奇地保证了每个合数只会被它的最小素因子筛掉,就把复杂度降到了O(N)。这代码让合数n=i×Prime[j+1]以及后面i×Primes[j+k](其中k是正整数)不被划掉。
    i%Primes[j]==0,可设n=i×Primes[j+1]=t×Primes[j]×Primes[j+1](其中t为正整数),可知n会被当i=t×Primes[j+1]被前面Primes[j]划掉了,后面的数同理。
  4. list[i*Primes[j]] = false;if(i % Primes[j] == 0)break;不可颠倒。假设\(n=a_1^{k_1}a_2^{k_2}...a_n^{k_n},k_1>1\),根据上面说明,n是由\(a_1^{k_1-1}a_2^{k_2}...a_n^{k_n}\)划掉,颠倒后,直接break,无法划掉,同样\(a_1^{k_1-1}a_2^{k_2}...a_n^{k_n}\)无法被\(a_1^{k_1-12}a_2^{k_2}...a_n^{k_n}\)划掉……这样就导致\(a_1a_2^{k_2}...a_n^{k_n}\)的所有倍数无法划掉。归纳来说就是合数的最小素数的幂次>1的数都被当作素数。

缺陷

虽然欧拉筛法的复杂度为O(n),但里面的%操作却会花费较多的时间。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!