数论-----素数篇

引言:

首先我们得明白什么是素数?从定义出发,素数又称质数。所谓素数是指除了1和它本身以外,不能被任何整数整除的数,例如17就是素数,因为它不能被2~16的任一整数整除

判断一个数是否是素数的解决方法:

  1. 朴素的方法(直接从2到n遍历,一旦发现某个数能被n整除,则一定是合数,从而不满足素数的定义)
bool check(int n){
	for(int i=2;i<=n;++i){
		if(n%i==0) return false;
	}
	return true;
}
  1. 试除法(就是说如果是合数的话,那么一定会有因数分布在sqrt(n)的范围内,这时我们就不需要遍历n个数字,而直接遍历sqrt(n)个数字,可以自己拿几个数字做下测试)
bool check(int n){
	for(int i=2;i*i<=n;++i){ //或者i<=sqrt(n),貌似效率慢点,不过可以拿个变量存入
		if(n%i==0) return false;
	}
	return true;
}
前面两种简单基础的方法,代码基本不变,不过效率上的还是有点欠缺,其中后者的方法也是我们常用,O(根号n)的时间复杂度,下面介绍的是更高效的方法
  1. 六素数法(,通俗一点说,除了2,3,以外对于任意的n,只有6n-1和6n+1有可能是素数。==注意,这里只是说有可能!==相信我们也可以知道,所有的素数都是分布在6的倍数左右两边,但是也有不是素数却又在六倍数的左右两边,这里我们就要特殊处理,具体看代码操作
    百度定义,可以look look
//时间复杂度应该为sqrt(n)/6
bool check( int n ){
    if (n <= 3) {  //处理小于3的情况
        return n > 1;
    }
    if ( n % 2 == 0 || n % 3 == 0 ) {  //如果
        return false;
    }
    /* 可以看下面这一组数,就可以理解下面的解法了,从5开始,
     * 比如第一次直接开始判断n是否可以
     * 整除5 和 7, 加6之后判断是否可以整除11 和13 。如此类推
     * (5,11), (7,13), (11,17), (13,19)
     */
    if(n%6==1 || n%6==5){  //必须是六倍数的左右两边
 		int k = sqrt(n)+1; //跟试除法没什么两样,只不过步长是6而已
 		for(int i=5;i<=k;i+=6;){
 			if(n%i==0 || n%(i+2)==0) return false;
		}
		return true;
 	} 
    return false;
}
  1. Miller_Rabin素性测试(Miller_Rabin的理论基础来源于费马小定理。值得一提的是费马小定理是数论四大定理之一。
    费马小定理:n是一个奇素数,a是任何整数(1≤ a≤n-1) ,则 a^(n-1)≡1(mod n)
    要测试n是否是一个素数,首先将n-1分解为(2^s) * d,在每次测试开始时,先随机选择一个介于[1, N - 1]的整数a,之后如果对于所有的r∈[0, s - 1],若a^d mod N ≠ 1且a((2r) * d) mod N ≠ -1,那么n就是一个合数,否则n有3/4的几率是素数。
    这也是为什么说这个算法只是素性测试了,他不能完全保证结果正确,但是我们可以通过多次测试来提高正确率,比如10次,20次)
    这个代码就不先写了,后面补上,这里有别人写的博客,还不错,感兴趣的了解一下

求区间内的质数呢?比如[1,100000]这样区间中的素数。

常用的方法就是欧拉筛素数法,那什么是欧拉筛呢?简单点来说就是在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的。

//int vis[],su[];   其中vis[]是要分析的整数区间,su[]存放素数
//时间复杂度为O(n)
void func(int n){
	int num = 0;
	for(int i=2;i<=n;++i){  //顺序分析整数区间的每个数,vis[]大小应该与n相等
		if(!vis[i])  su[++num] = i;  //这里表示将筛选中的数字送到素数表
		for(int j=1;j<=num;++j){  //遍历素数表,将i*素数表内的每个值对应的vis[]中做上标记,表示这些数字一定是合数,不应该放进素数表中
			if(i*su[j]>n)  break;
			vis[i*su[j]] = 1;  //做上标记,这样就不会放进素数表中
			if(i%su[j]==0) brreak;  //这一步很关键,主要是优化用的,前面我们说过欧拉筛的关键就是每个合数仅仅被它最小的质因数筛去,这里就保证这个特性
		}
	}
}
/* 举个例子,对上面最后一步的讲述,如8 12
此时素数表中有2 3 5 7
这时8*2==16,则表示vis[16]标记
如果此时再不break,则会在后面其他数字会出现重现性的计算,8*3==24,8*5==40,8*7==56
那么到12的时候,12*2==24,很显然24已经被标记过了,这里还继续计算,这样会导致效率低*/

当然你也可以用上面单个素数检测的方法来求区间内的素数,但是这样效率比较慢,实在要用就用六素数法或者Miller_Rabin素性测试

for(int i=first;i<=end;++i){
	//六素数法或者Miller_Rabin素性测试
}

你可能感兴趣的:(数据结构与算法知识基础以及进阶)