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

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

背景

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

暴力

方法一

  • 从素数的定义入手。
  • 一个数 i i 为素数当且仅当 i i 的因数只有 1 1 i i
  • 因此我们可以从 2 2 枚举到 i 1 i-1 ,如果 i m o d    = 0 i \mod 当前枚举的数=0 则说明 i i 的因数不只有 1 1 i i ,因此可以判断 i i 为合数。反之,如果除去 1 1 i i 之外的其它数都不可以整除 i i ,则可以判定 i i 为质数。
  • 时间复杂度: O ( n ) O(n) ,其中n为判定的数。

方法二

  • 发现对于一个数 i i ,如果 i = a b i=a*b ,则 a a b b 中必然至少有一个数小于等于 i \sqrt{i}
  • 因此我们可以得出,如果在 [ 2 , i ] [2,\sqrt{i}] 中找不到 i i 的因数,那么 [ i + 1 , i 1 ] [\sqrt{i}+1,i-1] 中必然也不会有 i i 的因数。
  • 因此把方法一改进一下,就可以做到 O ( n ) O(\sqrt{n}) 的时间复杂度。

优化

  • 有一个与它有关的重要定理:大于等于 5 5 的一组素数肯定是 6 x 1 6x-1 6 x + 1 6x+1 的形式,其中 x x 为正整数。
  • 证明可以从一个大于3的素数的因数不包含 2 2 3 3 来考虑。
  • 设待判定的数为 i i ,我们先判断 2 2 3 3 是不是 i i 的真因子,如果是就直接判为合数。
  • 否则,则 i i 必含有 > = 5 >=5 的质因子,我们枚举 6 x 1 6x-1 6 x + 1 6x+1 即可。
  • 这样可以使时间复杂度缩小6倍。(尽管好像没什么卵用)

筛法

  • 筛法的不同之处在于,它一般会把所有素数筛出来,因此它的时间复杂度会比较高,但在需要得知 [ 1 , n ] [1,n] 中的素数时,筛法占有绝对的优势。
  • 那些 O ( n 2 ) O ( n n ) O(n^2)、O(n \sqrt{n}) 的鬼畜筛,以及 O ( n 2 3 ) O(n^\frac{2}{3}) 的神仙筛这里并不提及。
  • 首先我们要知道,筛法的本质是用一个非 1 1 的数把它的倍数(不包含它本身)给筛掉。因为很显然那些倍数绝对是合数。

普通筛

  • 先定义一个bool数组 b z [ i ] bz[i] 代表 i i 是不是素数
  • 1 1 n n 枚举,然后枚举它的倍数,打上 f a l s e false 标记。
  • 发现这样子时间复杂度是调和级数,是 O ( n l o g 2 n ) O(nlog_2n) 的。
  • 代码大概长这样子:
	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 则说明 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})
  • 这样一来 5 e 6 5e6 如果用一开始的筛法会达到 1.1 1 0 8 1.1*10^8 级别,而埃氏筛法只会达到 2.2 1 0 7 2.2*10^7 级别,如果 n n 达到 6 e 6 6e6 7 e 6 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 表示的是素数的集合。
  • 你看罢,大惊曰:“这二重循环怎么看都不像线性的啊!”
  • 但有时候眼睛会出差错,我们需要严谨的证明。
  • 首先要清楚我们的目的——每个数只会被它的最小质因子给筛掉。
  • 欧拉筛最重要的一句话是:
			if(i%pri[j]==0) break;
  • 这是个啥东西呢?
  • 我们考虑一个合数 x x = i j i*j ,其中 j j 为素数,我们要证明 i i j j 的组合不会被break掉当且仅当 j j x x 的最小质因子。
  • 考虑反证法。
  • 如果 j j 不是最小质因子,设 x x 的最小质因子为 k k ,则 i m o d &ThinSpace;&ThinSpace; k = 0 i \mod k=0 ,因为有上面那条break语句的存在,所以我们第二重循环在枚举到素数 k k 时就会被break掉,根本不会枚举到 j j
  • 因此一个数只会被筛一次,又因为这个二重循环每一次必然会筛掉一个数,所以时间复杂度是线性的, O ( n ) O(n)
  • 悄悄告诉你:之所以看起来不想线性的,是因为它又常数!但不是特别大(2左右)。
  • 不过如果你写的有没的话常数是可以缩小的。

总结

  • 干嘛要写总结呢?
  • 总的来说,素数是一个非常奇妙的东西,如果连一个数是不是素数都不知道…也并不能代表什么,毕竟最近的题目越来越毒瘤了!
  • 所以你以为掌握这些方法就够了吗?NO!
  • Wilson定理:https://blog.csdn.net/fengqiyuka/article/details/100007632
发布了58 篇原创文章 · 获赞 12 · 访问量 8549

猜你喜欢

转载自blog.csdn.net/fengqiyuka/article/details/99963246