素数问题

Posted h_hg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了素数问题相关的知识,希望对你有一定的参考价值。

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

暴力方法

判断一个数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
    {
        const 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 log n)。如果用某种方法使得每个合数只被筛去一次就变成是线性的了。不妨规定每个合数只用其最小的一个质因数去筛,这便是欧拉筛了,时间复杂度 O(n).

原理:每一个合数都可以分解成若干个素数相乘,在这些素数中,必有最小的一个。如果我们要划掉\((1,n]\)里面所有合数,如果我们讲变量i从2变化到n,对每个i,我们都将i × i前面已知所有素数划掉,那么这些素数恰好是被划掉的合数的最小素数因子,又由于i是从2变化到n,所以能保证每个合数都能被划掉,

推广

如果把int范围的素数存起来,下次只需要使用二分法查找该数是否在列表里即可

//将一个合数N分解为若干个质数的积,Hint:N的大于sqrt(N)的质因子至多有一个(sqrt(n)指N的开方取整)
void divide(int n)
{
const int limit = sqrt(n);
int p = new int[limit];
int num = MakePrimes2_(p,limit);
for(int i = 0;i < num && n > 1;)
{
if(n % p[i] == 0)
{
std::cout << p[i];
n /= p[i];
if(n != 1)
std::cout << "
";
}
else
i++;
}
if(n > 1)//因为最多一个大于sqrt(n)的质数,就是这个
std::cout << n;
delete[] p;
}
int main()
{
/* const int max = 100;
int a[max];
int n = MakePrimes2_(a,max);
for(int i = 0;i < n;++i)
std::cout << a[i] << "\t";
std::cout << std::endl;
n = MakePrimes1_(a,max);
for(int i = 0;i < n;++i)
std::cout << a[i] << "\t";
std::cout << std::endl; */
return 0;
}

以上是关于素数问题的主要内容,如果未能解决你的问题,请参考以下文章

前 10000 个素数的最有效代码?

为啥我打印素数的代码会提前终止?

使用递归c ++找到最小的素数

在python中打印素数

C++判断素数的代码

Java中带指数的素数分解