筛素数以及判断数是否是素数

判断一个数字是否是素数:

解法1: O(n)

public static boolean isPrime(int n){
    if (n <= 3) {
        return n > 1;
    }
    for(int i = 2; i < n; i++){
        if (n % i == 0) {
            return false;
        }
    }
    return true;
}

解法2: 假如n是合数,必然存在非1的两个约数p1和p2,其中p1<=sqrt(n),p2>=sqrt(n)。由此我们可以改进上述方法优化循环次数。如下:

public static boolean isPrime(int n) {
    if (n <= 3) {
        return n > 1;
    }
    int sqrt = (int)Math.sqrt(n);
    for (int i = 2; i <= sqrt; i++) {
        if(n % i == 0) {
            return false;
        }
    }
    return true;
}

解法3:

我们继续分析,其实质数还有一个特点,就是它总是等于 6x-1 或者 6x+1,其中 x 是大于等于1的自然数。

    如何论证这个结论呢,其实不难。首先 6x 肯定不是质数,因为它能被 6 整除;其次 6x+2 肯定也不是质数,因为它还能被2整除;依次类推,6x+3 肯定能被 3 整除;6x+4 肯定能被 2 整除。那么,就只有 6x+1 和 6x+5 (即等同于6x-1) 可能是质数了。所以循环的步长可以设为 6,然后每次只判断 6 两侧的数即可。
 对于输入的自然数 n 较小时,也许效果不怎么明显,但是当 n 越来越大后,该方法的执行效率就会越来越明显了。

public static boolean isPrime(int num) {
    if (num <= 3) {
        return num > 1;
    }
    // 不在6的倍数两侧的一定不是质数
    if (num % 6 != 1 && num % 6 != 5) {
        return false;
    }
    int sqrt = (int) Math.sqrt(num);
    for (int i = 5; i <= sqrt; i += 6) {
        if (num % i == 0 || num % (i + 2) == 0) {
            return false;
        }
    }
    return true;
}

筛素数:

一、一般筛素数(埃拉托斯特尼筛法)

此筛选法的时间复杂度是O(nloglogn)

int vis[maxn];
memset(vis,1,sizeof(vis));
void isprime()
{
    int prime[maxn],cnt=0;
    for(int i=2;i<=sqrt(N);i++)
    {
        if(vis[i])
        {
            prime[cnt++]=i;
            for(int j=i*i;j<=N;j+=i)
                vis[j]=0;//i是素数,则下一个起点是i*i,把后面的所有的i*i+2*n*i筛掉
        }
    }
}

这种方法比较好理解,初始时,假设全部都是素数,当找到一个素数时,显然这个素数乘上另外一个数之后都是合数(注意上面的 i*i ,  比 i*2 要快点 ),把这些合数都筛掉。

但仔细分析能发现,这种方法会造成重复筛除合数,影响效率。比如10,在i=2的时候,k=2*15筛了一次;在i=5,k=5*6 的时候又筛了一次。所以,也就有了快速线性筛法。

二、线性筛素数(欧拉筛法)
线性筛,复杂度为O(n)。与埃氏筛相比,不会对已经被标记过的合数再进行重复标记,故效率更高。欧拉筛将合数分解为 (最小质因数 * 一个合数) 的形式,通过最小质因数来判断当前合数是否已经被标记过。

int vis[maxn];
memset(vis,0,sizeof(vis));
void isprime()
{
    int prime[maxn],cnt=0;//cnt用来计数,prime数组保存素数 
    for(int i=2;i<=n;i++)
    {
        if(!vis[i])prime[cnt++]=i;//如果未被标记过,则表示为素数 
        for(int j=0;j<cnt && i*prime[j]<=n;j++)//当标记的合数超出范围则退出 
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)break;//关键步骤 
        }
    }
}

难点就在于对if (i % prime[j] == 0)这步的理解,意思是每个数保证被它的最小质因数筛出,即当i是prime[j]的整数倍时,记 m = i / prime[j],那么 i * prime[j+1] 就可以变为 (m * prime[j+1]) * prime[j],这说明 i * prime[j+1] 是 prime[j] 的整数倍,不需要再进行标记(在之后会被 prime[j] * 某个数 标记),对于 prime[j+2] 及之后的素数同理,直接跳出循环,这样就避免了重复标记。
参考文献:

1 https://blog.csdn.net/qq_39826163/article/details/81395306

2 https://blog.csdn.net/afei__/article/details/80638460

猜你喜欢

转载自www.cnblogs.com/xianbin7/p/10710802.html
今日推荐