素数判定方法

yuyu888 于 2021-02-20 发布

素数的定义

如果一个数不能整除比它小的任何素数,那么这个数就是素数

素数的应用

素数有很好的数学特性, 很多数学理论都用到了素数, 计算机里常用的产生随机数,hash算法,RSA非对称加密算法等都用到了素数

如何判断一个数是否是素数

朴素算法

根据素数定义判定
给定一个数n,i 从 2 开始取值,直到 n - 1(取整数),如果 n % i != 0 , n 就是素数
实际上 除了1以外,任何合数最小的因子就是2,那最大的因子就是 n/2, 那我们就遍历到 n/2就足够了
再进一步其实我们遍历到 sqrt(n); 就行了

go实现

func IsPrimeLow(n int) bool {
	if n < 2 {
		return false
	}
	max := int(math.Sqrt(float64(n)))
    //max := n/2
	for i := 2; i <= max; i++ {
		if n%i == 0 {
			return false
		}
	}
	return true
}

素数判别法

根据上面的例子如果我们输入的数是67, 那么Max 是sqrt(67) 取整得8; 我们要挨个去用{2,3,4,5,6,7,8}去试, 其中{4,6,8}都是2的倍数;假如67不能被2整除, 那么一定不能被{4,6,8}整除,后面的对{4,6,8}的判定都是多余的, 所以算法可以改进为:

给定一个数n,循环小于sqrt(n)的所有素数,取当前值为i, 如果 n % i != 0 , n 就是素数

该算法的关键在于求取指定数字以内的所有素数;(感觉是个圈)
大致思路:
1、我们可以先设定小于10的素数集合{2,3,5,7} 那么我们就可以遍历10~100的数 通过该判定方法求出100以内的质数集合
2、拿着100以内的质数集合,取求取小于10000的质数集合
3、依次类推,求出100 000 000 以内的质数集合
4、10000以内的质数是有限的,为1229个,可以先求出来,写死在一个数组里,以此为基础,以空间换时间, 就能轻松判定1亿以内的任意数字是否为素数
5、UInt64 代表无符号的最大值为: 18446744073709551615 开平方得到:4294967296 那么我们也可以求出4294967296内的所有质数,以此为基础,解决目前计算机常态下的素数运算问题

埃拉托斯特尼(Eratosthenes)筛法

该方法是古希腊数学家埃拉托斯特尼提出,用于获取指定数字n以内的所有素数集合, 他其实是利用了空间换时间的方式; 先从2开始累加生成一个数组a包含小于n的所有整数;然后从a[i+1]遍历除以a的第一个元素a[0]=2;如果能被2整除,则标记为false,循环结束后再进入下一轮循环,除以第一个没有被标记的数字,如果之前标记过,就跳过否则,判断是否被整除,如果能够整除则标记为fales, 知道被除数大于sqrt(n),打印出所有未被标记的数字

php的实现(为什么要说php好呢?只用专注逻辑实现,写出来的东西更像其他语言的伪代码,结构清晰)

function Primenumber($n){
	if($n<=1){
		return array();
	}
    // 构造数组
    $arr = [];
	for($i=2;$i<=$n;$i++){
		$arr[$i]=true;
	}

    // 逐个标记
	$p=2;
    $maxp = intval(sqrt($n));
	while($p<=$maxp){
		$nextp = 0;
        // 遍历
		for($i=$p+1;$i<=$n;$i++){
			if($arr[$i]==true && $i%$p==0){
				$arr[$i]=false; //标记
			}else{
                // 产生下一个循环被除数
				if($arr[$i]==true){
					$nextp = $nextp==0?$i:$nextp;
				}			
			}
		}
		$p =$nextp;
	}

    // 获取为true的标记位
	$primes=[];
	for($i=2;$i<=$n;$i++){
		if($arr[$i]==true){
			$primes[] = $i;
		}
	}
	return $primes;
}

这个方法的缺点就是空间浪费太大,其实仔细分析下
此筛法的原理是:想要取得不大于n的自然数内所有的素数,就要把小于√n的所有素数的倍数剔除掉。

go的实现
以下算法可以节省一定的空间,但是要计算若干次sqrt(n), 这里我用了自带函数,实际求解sqrt(n), 也不是很容易,一定程度上还是比埃拉托斯特尼筛法要低;

func Primenumber(num uint64, primes []uint64) []uint64 {
	if num < 2 {
		return nil
	}
	maxp := uint64(math.Sqrt(float64(num)))
	max_prime := primes[len(primes)-1]
	if max_prime < maxp {
		primes = Primenumber(maxp, primes)
	}
	primeList := primes
	for i := primes[len(primes)-1] + 1; i <= num; i++ {
		isPrime := true
		for _, n := range primes {
			if i%n == 0 {
				isPrime = false
				break
			}
		}
		if isPrime == true {
			primeList = append(primeList, i)
		}
	}
	return primeList
}

质数分布的规律

大于等于5的质数一定和6的倍数相邻
例如5和7,11和13,17和19等等;

证明:令x≥1,将大于等于5的自然数表示如下:
··· 6x-1,6x,6x+1,6x+2,6x+3,6x+4,6x+5,6(x+1),6(x+1)+1 ···
可以看到,不和6的倍数相邻的数为6x+2,6x+3,6x+4,由于2(3x+1),3(2x+1),2(3x+2),所以它们一定不是素数,再除去6x本身,显然,素数要出现只可能出现在6x的相邻两侧

但是6x两侧的数也不一定是质数,还得判断是否是6y+1, 6y+5的倍数( y < x ) 例如25,是64+1, 在64的右边,但是是6*0+5的倍数就不是质数

大素数判别

上述方法里我们可以很容易判别UInt64以内的素数,但是如果要对一个工业级的大素数进行判别就不那么容易了

比如:RSA 算法里需要找到一个512位的大素数,该怎么办?

思路很简单,就是找到一个512位的数字区间, 遍历每一个数,检查其是否为素数,如果是就返回

但是如何判别一个512位的数字是否是素数呢? 用之前的方法效率极其低下

一般做法是经过若干次米勒-罗宾(Miller-Robin)素性测试,检查通过就认为其为素数

这里不做展开,计划单独开一篇文章做详细阐述 https://www.cnblogs.com/Norlan/p/5350243.html