判断素数的方法(普通篇)

  • 不知名的东西: [ 1 , n ] [1,n] [1,n]中的素数大约有 n ln ⁡ n \frac{n}{\ln n} lnnn个。

背景

  • 没有背景。
  • 素数是数学中一种十分重要的数字,它的重要性使得它在信息学领域中也有广泛的应用。其中有一种很常见的题目就是:
  • “给你一个数 i i i,判断其是否是素数。”

暴力

方法一
  • 从素数的定义入手。
  • 一个数 i i i为素数当且仅当 i i i的因数只有 1 1 1 i i i
  • 因此我们可以从 2 2 2枚举到 i − 1 i-1 i1,如果 i m o d    当 前 枚 举 的 数 = 0 i \mod 当前枚举的数=0 imod=0则说明 i i i的因数不只有 1 1 1 i i i,因此可以判断 i i i为合数。反之,如果除去 1 1 1 i i i之外的其它数都不可以整除 i i i,则可以判定 i i i为质数。
  • 时间复杂度: O ( n ) O(n) O(n),其中n为判定的数。
方法二
  • 发现对于一个数 i i i,如果 i = a ∗ b i=a*b i=ab,则 a a a b b b中必然至少有一个数小于等于 i \sqrt{i} i
  • 因此我们可以得出,如果在 [ 2 , i ] [2,\sqrt{i}] [2,i ]中找不到 i i i的因数,那么 [ i + 1 , i − 1 ] [\sqrt{i}+1,i-1] [i +1,i1]中必然也不会有 i i i的因数。
  • 因此把方法一改进一下,就可以做到 O ( n ) O(\sqrt{n}) O(n )的时间复杂度。
优化
  • 有一个与它有关的重要定理:大于等于 5 5 5的一组素数肯定是 6 x − 1 6x-1 6x1 6 x + 1 6x+1 6x+1的形式,其中 x x x为正整数。
  • 证明可以从一个大于3的素数的因数不包含 2 2 2 3 3 3来考虑。
  • 设待判定的数为 i i i,我们先判断 2 2 2 3 3 3是不是 i i i的真因子,如果是就直接判为合数。
  • 否则,则 i i i必含有 > = 5 >=5 >=5的质因子,我们枚举 6 x − 1 6x-1 6x1 6 x + 1 6x+1 6x+1即可。
  • 这样可以使时间复杂度缩小6倍。(尽管好像没什么卵用)

筛法

  • 筛法的不同之处在于,它一般会把所有素数筛出来,因此它的时间复杂度会比较高,但在需要得知 [ 1 , n ] [1,n] [1,n]中的素数时,筛法占有绝对的优势。
  • 那些 O ( n 2 ) 、 O ( n n ) O(n^2)、O(n \sqrt{n}) O(n2)O(nn )的鬼畜筛,以及 O ( n 2 3 ) O(n^\frac{2}{3}) O(n32)的神仙筛这里并不提及。
  • 首先我们要知道,筛法的本质是用一个非 1 1 1的数把它的倍数(不包含它本身)给筛掉。因为很显然那些倍数绝对是合数。
普通筛
  • 先定义一个bool数组 b z [ i ] bz[i] bz[i]代表 i i i是不是素数
  • 1 1 1 n n n枚举,然后枚举它的倍数,打上 f a l s e false false标记。
  • 发现这样子时间复杂度是调和级数,是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)的。
  • 代码大概长这样子:
	memset(bz,true,sizeof(bz));
	for(int i=1;i<=n;i++)
		for(int j=2;n/j>=i;j++) bz[i*j]=false;
  • 发现memset会耗掉些许时间,也可以写成这样子:
	for(int i=1;i<=n;i++)
		for(int j=2;n/j>=i;j++) bz[i*j]=true;
  • 最后 b z [ i ] = f a l s e bz[i]=false bz[i]=false则说明 i i i为素数。
埃氏筛法
  • 我们发现一个数可能会被筛很多次,这样太浪费时间了。
  • 如果只拿素数去筛它的倍数,这样是否可行呢?
  • 答案是:YES。因为一个合数的因数必有至少一个数为素数。所以素数可以筛掉所有的合数。
  • 代码:
	for(int i=1;i<=n;i++)
		if(!bz[i]){
			for(int j=2;n/j>=i;j++)
				bz[i*j]=true;
		}
  • 可以证明时间复杂度约为 O ( n l o g 2 l o g 2 n ) O(nlog_2{log_2n}) O(nlog2log2n)
  • 这样一来 5 e 6 5e6 5e6如果用一开始的筛法会达到 1.1 ∗ 1 0 8 1.1*10^8 1.1108级别,而埃氏筛法只会达到 2.2 ∗ 1 0 7 2.2*10^7 2.2107级别,如果 n n n达到 6 e 6 6e6 6e6 7 e 6 7e6 7e6级别,优化的效果会更明显。
欧拉筛法(线性筛)
  • 看标题就知道时间复杂度…
  • 如果一个合数的约数有多个素数,它仍会被筛很多次。
  • 考虑优化到极致——一个合数只会被筛一次。
  • 我们就让它只会被它的最小质因子给筛掉吧!
  • 好像有点难…,先放代码:
	for(int i=1;i<=n;i++){
		if(!bz[i]) pri[++pri[0]]=i;
		for(int j=1;j<=pri[0];j++){
			if(n/pri[j]<i) break;
			bz[i*pri[j]]=true;
			if(i%pri[j]==0) break;
		}
	}
  • 其中 p r i pri pri表示的是素数的集合。
  • 你看罢,大惊曰:“这二重循环怎么看都不像线性的啊!”
  • 但有时候眼睛会出差错,我们需要严谨的证明。
  • 首先要清楚我们的目的——每个数只会被它的最小质因子给筛掉。
  • 欧拉筛最重要的一句话是:
			if(i%pri[j]==0) break;
  • 这是个啥东西呢?
  • 我们考虑一个合数 x x x= i ∗ j i*j ij,其中 j j j为素数,我们要证明 i i i j j j的组合不会被break掉当且仅当 j j j x x x的最小质因子。
  • 考虑反证法。
  • 如果 j j j不是最小质因子,设 x x x的最小质因子为 k k k,则 i m o d &ThinSpace;&ThinSpace; k = 0 i \mod k=0 imodk=0,因为有上面那条break语句的存在,所以我们第二重循环在枚举到素数 k k k时就会被break掉,根本不会枚举到 j j j
  • 因此一个数只会被筛一次,又因为这个二重循环每一次必然会筛掉一个数,所以时间复杂度是线性的, O ( n ) O(n) O(n)
  • 悄悄告诉你:之所以看起来不想线性的,是因为它又常数!但不是特别大(2左右)。
  • 不过如果你写的有没的话常数是可以缩小的。

总结

  • 干嘛要写总结呢?
  • 总的来说,素数是一个非常奇妙的东西,如果连一个数是不是素数都不知道…也并不能代表什么,毕竟最近的题目越来越毒瘤了!
  • 所以你以为掌握这些方法就够了吗?NO!
  • Wilson定理:https://blog.csdn.net/fengqiyuka/article/details/100007632
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值