算法之路之素数筛法

素数筛法

更好的阅读体验
现在提出一个问题:求出1到n的所有素数?

暴力做法

暴力的思想判断某个数是否是一个质数

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

我们可以分析一下这个算法的时间复杂度

对于每一个质数来说需要遍历到它本身才能知道是否一个质数

时间复杂度为 o ( n 2 ) o(n^2) o(n2)

我们设法改进,发现只需要遍历到a的根号即可

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

埃氏筛

在筛法过程中,我们发现判断时如果这个数的最小因数为 p p p,则每次遍历时需要从 2 2 2 p p p

为了改进筛法,我们找到有可能为因子的数,这就是埃氏筛的思想

我们写出到 2 到 16 2到16 216
2     3     4     5     6     7     8     9     10     11     12     13     14     15     16 2~~~3~~~4~~~5~~~6~~~7~~~8~~~9~~~10~~~11~~~12~~~13~~~14~~~15~~~16 2   3   4   5   6   7   8   9   10   11   12   13   14   15   16
开筛!

找到第一个没被筛过的数2,筛掉2的倍数
2     3     4     5     6     7     8     9     10     11     12     13     14     15     16 2~~~3~~~\cancel{4}~~~5~~~\cancel{6}~~~7~~~\cancel{8}~~~9~~~\cancel{10}~~~11~~~\cancel{12}~~~13~~~\cancel{14}~~~15~~~\cancel{16} 2   3   4    5   6    7   8    9   10    11   12    13   14    15   16

继续,发现是 3 3 3
2     3     4     5     6     7     8     9     10     11     12     13     14     15     16 2~~~3~~~\cancel{4}~~~5~~~\cancel{6}~~~7~~~\cancel{8}~~~\cancel{9}~~~\cancel{10}~~~11~~~\cancel{12}~~~13~~~\cancel{14}~~~\cancel{15}~~~\cancel{16} 2   3   4    5   6    7   8    9    10    11   12    13   14    15    16
继续,发现是 5 5 5, 5 5 5 16 \sqrt{16} 16 大,所以不用在编历了,至此素数被筛出来了

代码如下

int[] isPrime=new int[N];
public static void init(int n)
{
    for(int i=2;i<=Math.sqrt(n);i++)
    {
        if(!isPrime[i])
        {
            for(int j=i*i;j<=n;j+=i)
            {
                isPrime[j]=true;
			}
        }
    }
}

埃氏筛的时间复杂度是 o ( n l o g l o g n ) o(nloglog_n) o(nloglogn),已经是很不错的算法了,

但我们在埃氏筛的过程中,可以发现有的数据被重复筛到了,例如12同时被2和3筛到

为了改进这个问题,我们希望每个数只被筛一次,2和3都是12的因子,那么12被谁筛呢?

我们希望每个数被它的最小质因子筛掉,这就是线性筛的思想

线性筛

线性筛在埃氏筛的基础上还维护了一个质数表
2     3     4     5     6     7     8     9     10     11     12     13     14     15     16 p r i m e s : ( ) 2~~~3~~~4~~~5~~~6~~~7~~~8~~~9~~~10~~~11~~~12~~~13~~~14~~~15~~~16 \\primes:() 2   3   4   5   6   7   8   9   10   11   12   13   14   15   16primes:()
遍历,找到第一个未被划掉的数 2 2 2,加入素数表中
2     3     4     5     6     7     8     9     10     11     12     13     14     15     16 p r i m e s : ( 2 , ) 2~~~3~~~4~~~5~~~6~~~7~~~8~~~9~~~10~~~11~~~12~~~13~~~14~~~15~~~16 \\primes:(2,) 2   3   4   5   6   7   8   9   10   11   12   13   14   15   16primes:(2,)
2 2 2乘以素数表中的数,然后划掉结果
2     3     4     5     6     7     8     9     10     11     12     13     14     15     16 p r i m e s : ( 2 , ) 2~~~3~~~\cancel{4}~~~5~~~6~~~7~~~8~~~9~~~10~~~11~~~12~~~13~~~14~~~15~~~16 \\primes:(2,) 2   3   4    5   6   7   8   9   10   11   12   13   14   15   16primes:(2,)
下一个数是 3 , 3 3,3 33未被划掉,加入素数表中,划掉 6 , 9 6,9 6,9
2     3     4     5     6     7     8     9     10     11     12     13     14     15     16 p r i m e s : ( 2 , 3 ) 2~~~3~~~\cancel{4}~~~5~~~\cancel{6}~~~7~~~8~~~\cancel{9}~~~10~~~11~~~12~~~13~~~14~~~15~~~16 \\primes:(2,3) 2   3   4    5   6    7   8   9    10   11   12   13   14   15   16primes:(2,3)
继续,下一个数是4,4被划掉不加入素数表,但是参与运算
2     3     4     5     6     7     8     9     10     11     12     13     14     15     16 p r i m e s : ( 2 , 3 ) 2~~~3~~~\cancel{4}~~~5~~~\cancel{6}~~~7~~~\cancel{8}~~~\cancel{9}~~~10~~~11~~~12~~~13~~~14~~~15~~~16 \\primes:(2,3) 2   3   4    5   6    7   8    9    10   11   12   13   14   15   16primes:(2,3)
此处我们划掉了 8 8 8,但我们不划掉 12 12 12

我们发现如果划掉 12 12 12,则是由 4 , 3 4,3 4,3因子划掉的,而12的最小质因子是2,违背了由最小质因子划掉的原则。而 4 , 3 4,3 4,3之所以能够划掉12,是因为4中隐含了质因子2,因此我们可以得出结论,当某个数隐含了某个质因子时,这个数就具有代替这个质因子划掉某些数的功能,因此我们要停止操作

继续操作,找到下一个数5,5未被筛掉,加入到素数表,划去10,15
2     3     4     5     6     7     8     9     10     11     12     13     14     15     16 p r i m e s : ( 2 , 3 , 5 ) 2~~~3~~~\cancel{4}~~~5~~~\cancel{6}~~~7~~~\cancel{8}~~~\cancel{9}~~~\cancel{10}~~~11~~~12~~~13~~~14~~~\cancel{15}~~~16 \\primes:(2,3,5) 2   3   4    5   6    7   8    9    10    11   12   13   14   15    16primes:(2,3,5)
以此类推,得到最终结果

… \dots
2     3     4     5     6     7     8     9     10     11     12     13     14     15     16 p r i m e s : ( 2 , 3 , 5 , 7 , 11 , 13 ) 2~~~3~~~\cancel{4}~~~5~~~\cancel{6}~~~7~~~\cancel{8}~~~\cancel{9}~~~\cancel{10}~~~11~~~\cancel{12}~~~13~~~\cancel{14}~~~\cancel{15}~~~\cancel{16} \\primes:(2,3,5,7,11,13) 2   3   4    5   6    7   8    9    10    11   12    13   14    15    16 primes:(2,3,5,7,11,13)
代码如下

int[] Primes=new int[N];
boolean[] isPrime=new boolean[N];
int cnt=0;
public static void init(int n)
{
    for(int i=2;i<=n;i++)
    {
        if(!isPrime[i])
        {
            Primes[cnt++]=i;
            for(int j=0;j<cnt;j++)
            {
                if(j*Primes[j]>n)
                    break;
                isPrime[i*Primes[j]]=true;
                if(i%Prime[j]==0)
                    break;
            }
        }
    }
}

线性筛的时间复杂度为 o ( n ) o(n) o(n)

线性筛法的证明

要证明线性筛法是正确的,我们可以证明对于每一个合数,它一定能够被筛到并且只被筛到一次

为了简化证明,这里我们给出算术基本定理(唯一基本定理)

p p p 是素数,则对于每一个正整数 a a a

有如下的唯一表示方法
a = p 1 α 1 p 2 α 2 … p s α s a=p_1^{\alpha_1}p_2^{\alpha_2}\dots p_s^{\alpha_s} a=p1α1p2α2psαs
不妨设
p 1 < p 2 < ⋯ < p s p_1<p_2<\dots<p_s p1<p2<<ps
p 1 p_1 p1为最小质因子

我们使用反证法证明

  1. 某一个合数不能被筛到

​ 已知 p 1 p_1 p1一定会被加入到质数表中

​ 当遍历到 p 1 α 1 − 1 p 2 α 2 … p s α s p_1^{\alpha_1-1}p_2^{\alpha_2}\dots p_s^{\alpha_s} p1α11p2α2psαs时,由于该数的质因子最小为 p 1 p_1 p1,因此一定会到乘以 p 1 p_1 p1这一步,则合数一定会被筛掉

  1. 某一个合数被筛到两次

​ 由1可知,合数一定被筛到一次,若合数被筛到

​ 假设遍历到了某个数 i i i

​ 则有
i ∗ p k = a ( p k 为 素 数 表 中 的 某 个 素 数 ) i*p_k=a(p_k为素数表中的某个素数) ipk=a(pk)

​ 又跟据
a = p 1 α 1 p 2 α 2 … p s α s a=p_1^{\alpha_1}p_2^{\alpha_2}\dots p_s^{\alpha_s} a=p1α1p2α2psαs
​ 则 p k 为 p 1 … p s p_k为p_1\dots p_s pkp1ps中的某一个

​ 则 i i i可以写成如下形式
i = a p k = p 1 α 1 p 2 α 2 … p k α k − 1 … p s α s i=\dfrac{a}{p_k}=p_1^{\alpha_1}p_2^{\alpha_2}\dots p_k^{\alpha^k-1}\dots p_s^{\alpha_s} i=pka=p1α1p2α2pkαk1psαs
​ 则可知 i i i中含有质因子 p 1 p_1 p1 i i i在第一次乘以质数表中的 p 1 p_1 p1后结束,因此不存在第二次筛去

​ 综上得证线性筛法的正确性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值