ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

素数筛法详解:埃氏筛和欧拉筛

2020-03-05 16:02:02  阅读:372  来源: 互联网

标签:埃氏 筛法 int 质数 详解 static 1e7 primes 合数


文章目录

超级详细的基础算法和数据结构合集:
https://blog.csdn.net/GD_ONE/article/details/104061907

摘要

本文主要介绍埃氏筛法欧拉筛法
之前讲了怎么判断一个数是不是质数,现在求区间[1,1e7][1, 1e7][1,1e7]内所有的质数。我们将用到埃氏筛和欧拉筛。

埃式筛

埃拉托斯特尼筛法,简称埃氏筛。

学习埃氏筛之前,我们先看一下暴力筛法,即对每个数都用试除法判断其是不是质数:

暴力筛法:

static final int N = 1e7 + 5;
int st[N]; // 初始化为0, 0表示质数,1表示合数

for(int i = 2; i <= n; i++){
	for(int j = 2; j <= i / j; j++){//试除法
		if(i % j == 0){
			st[i] = 1; // 合数,标记为1 
		}
	}
}

这种方式无疑是最慢的,我们看一下如何加快,换一种思路:一个质数的倍数一定是合数,所以,假设PPP是质数,我们可以筛掉区间[1,1e7][1,1e7][1,1e7]中所有PPP的倍数。
先看个例子,对于数列1~11:
在这里插入图片描述
先筛去2的倍数:
在这里插入图片描述
然后筛去3的倍数:
在这里插入图片描述
然后筛去5的倍数:
在这里插入图片描述
至此,1~11内的所有合数都被筛完了, 2 3 5 7 11是数列中的质数。
为什么这样能筛去所有的合数呢,因为一个合数一定能被分解为几个质数的幂的乘积,并且这个数的质因子一定是小于它本身的,所以当我们从小到大将每个质数的倍数都筛去的话,当遍历到一个合数时,它一定已经被它的质因子给筛去了

埃氏筛代码:

static final int N = 1e7 + 5;
static int[] st = new int[N];
public static void E_sieve(int  n){

	for(int i = 2; i <= n; i++)
	{
		if(st[i] == 0)
		{
			for(int j = 2 * i; j <= n; j += i)
			    st[j] = 1; // j是i的一个倍数,j是合数,筛掉。
		}
	}
	
}

以上代码的时间复杂度为:O(nloglog2n)O(nloglog_2n)O(nloglog2​n);
我们还可以对其进行优化:

  1. 我们会先筛2的所有倍数,然后筛3的所有倍数,但筛除3的倍数时,我们还是从3的2倍开始筛,其实3 * 2 ,已经被2 * 3时筛过了
    又比如说筛5的倍数时,我们从5的2倍开始筛,但是5 * 2会先被2 * 5筛去, 5 * 3会先被3 * 5会筛去,5 * 4会先被2 * 10筛去,所以我们每一次只需要从i*i开始筛,因为(2,3,…,i - 1)倍已经被筛过了。

  2. 另外,判断一个数nnn是不是质数,我们只判断[2,n][2, \sqrt{n} ][2,n​]内有没有它的因子。在筛合数的时候,我们也可以这样做,因为一个合数的最小质因子一定小于等于n\sqrt{n}n​。
    所以对于区间[1,1e7][1, 1e7][1,1e7],最大的合数是1e71e71e7, 它的最小质因子一定是小于等于1e7\sqrt{1e7}1e7​,区间内其他的合数的最小质因子也一定是小于等于1e7\sqrt{1e7}1e7​的,所以只需要用[1,1e7][1, \sqrt{1e7}][1,1e7​]中的质数就可以筛去[1,1e7][1, 1e7][1,1e7]中所有的合数。

优化后的埃式筛:

static final int N = 1e7 + 5;
static int[] st = new int[N];
public static void E_sieve(int  n){

	for(int i = 2; i <= n / i; i++)
	{
		if(st[i] == 0)
		{
			for(int j = i * i; j <= n; j += i)
			    st[j] = 1; // j是i的一个倍数,j是合数,筛掉。
		}
	}
	
}

优化后的时间复杂度可以近似看成O(n)O(n)O(n)了。
但是,我们还可以更快,那就是欧拉筛。

欧拉筛

欧拉筛,又称为线性筛,时间复杂度为O(n)O(n)O(n)。
先看下代码再看解析:

static final int N = 1e7 + 5;
static int[] st = new int[N], primes = new int[N];
void ola(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (st[i] == 0) primes[cnt ++ ] = i;//将质数存到primes中
        for (int j = 0; primes[j] <= n / i; j ++ )//要确保质数的第i倍是小于等于n的。
        {
            st[primes[j] * i] = 1;
            if (i % primes[j] == 0) break;
        }
    }
}

埃氏筛是筛去每个质数的倍数,但难免,会有合数会被其不同的质因子多次重复筛去。这就造成了时间浪费。

比如说: 120=2335120 = 2^3 * 3 * 5120=23∗3∗5
120 会被2筛去一次, 3筛去一次, 5筛去一次。
多做了两次不必要的操作。

那么我们如何确保120只被2筛掉呢?
在埃氏筛中我们用了一个循环来筛除一个质数的所有倍数,即对于p来说,筛除数列: 2p,3p,...,kp2 * p , 3 * p, ... , k*p2∗p,3∗p,...,k∗p
另外,我们是从小到大枚举区间中的每个数的,数列是:2,3,4,...,n2,3,4,...,n2,3,4,...,n
对比两个数列:

2p,3p,...,kp2*p, 3 * p, ... , k*p2∗p,3∗p,...,k∗p
2 3.... n2, 3,.... n2, 3,.... n

会发现,第二个数列是第一个数列的系数。
所以,我们不需要用一个for循环去筛除一个倍数的所有质数,我们将质数存储到primes[]中,然后枚举到第i个数时,筛去primes[j] * i

如果 i%primes[j]==0i \% primes[j] == 0i%primes[j]==0 , 我们就结束内层循环,为什么呢?
下面的很重要:
因为:

1 : i%primes[j]==0i \% primes[j] == 0i%primes[j]==0
2 : primes[j]k=iprimes[j] * k = iprimes[j]∗k=i

设:
3: primes[j+1]i=Xprimes[j+1] * i = Xprimes[j+1]∗i=X

将2式带入3中得: primes[j+1]primes[j]k=Xprimes[j+1] * primes[j] *k = Xprimes[j+1]∗primes[j]∗k=X

因为:primes[j+1]>primes[j]primes[j+1] > primes[j]primes[j+1]>primes[j], 所以:primes[j+1]k>iprimes[j+1]*k > iprimes[j+1]∗k>i

primes[j+1]k=iprimes[j+1]*k = i'primes[j+1]∗k=i′

则:4:primes[j]i=Xprimes[j]*i' = Xprimes[j]∗i′=X

所以如果用3式筛去XXX的话,当iii等于ii'i′时,XXX又会被4式筛去一次,为了确保合数只被最小质因子筛掉,最小质因子要乘以最大的倍数,即要乘以最大的iii, 所以不能提前筛。

比如说 1 ,2,3,4,5,6,7,8,9,10,11, 12
i==4i == 4时:i==4时: primes = {2, 3}
此时 i%2==0i\%2 == 0i%2==0, 如果不结束内层循环的话, 12会被343*43∗4筛掉, 当i==6i == 6i==6时,12又会被262*62∗6筛掉。

欧拉筛的核心思想就是确保每个合数只被最小质因数筛掉。或者说是被合数的最大因子筛掉。

例题:
筛质数

代码:

import java.io.*;
import java.util.*;

public class Main{
    static final int N = 10000005;
    static int[] primes = new int[N];
    static int[] st = new int[N];
    
    static int cnt = 0;
    public static void ola(int n){
      
        for(int i = 2; i <= n; i++)
        {
            if(st[i] == 0)	primes[cnt++] = i;
            
            for(int j = 0; primes[j] <= n / i; j++)
            {
                st[primes[j]*i] = 1;
                if(i % primes[j] == 0) break;
            }
        }
    }
    
    public static void main(String[] args){
       Scanner in = new Scanner(new InputStreamReader(System.in));
       int a;
       a = in.nextInt();
       
       ola(a);
       System.out.println(cnt);
    }
}

标签:埃氏,筛法,int,质数,详解,static,1e7,primes,合数
来源: https://blog.csdn.net/GD_ONE/article/details/104660294

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有