zoukankan      html  css  js  c++  java
  • 4.质数判定和质数筛法(埃拉托色尼筛选法,线性筛法/欧拉筛法)

    质数检验

    引题
    质数指约数仅为1及其本身的自然数。比如8个最小的质数为2,3,5,7,11,13,17,19。注意,1不是质数。
    请编写一个程序,输入n个整数,输出其中质数的个数。

    输入:第一行输入n。接下来1行给出n个整数。
    输出:输出质数的个数,占1行。
    限制1<=b<=100002<=<=1081<=b<=10000,2<=给出的整数<=10^8
    输入示例
    6
    2 3 4 5 6 7
    输出实例
    4

    1. 初学者的简单算法

    检验整数x是否为质数:检查整数x能否被2到x-1的整数整除。

    bool isPrime( int x )
    {
    	if( x<=1 ) return false;
    	for(int i=2;i<x;++i)
    		if( x%i==0 )
    			return false;
    	return true;
    }
    

    可以得知,上述算法对于单一数据其复杂度为O(x),算法整体的复杂度与xi(i=1,2,,n)x_i(i=1,2,…,n)的总和成正比,显然无法在限制时间内输出答案。我们需要考虑一个更高效的方法。

    2. 初学者优化算法
    1. 除2以外所有的偶数都不是质数,这样就能将复杂度减少一半。
    2. 在检查x时,由于x不可能被大于x2frac{x}{2}的整数整除,这就又减少了一半复杂度。

    但这些小技巧并不能撼动该算法复杂度为O(x)的本质。

    3. 性质:若n为合数,则必有质数p|n,且p&lt;=np&lt;=sqrt{n}

    在检验质数的时候,我们可以利用“合数x拥有满足p&lt;=xp&lt;=sqrt{x}的质因数p”这一性质。
    举个例子,检验31是否为质数时,只需要看31能否被2到6的整数整除即可。如果7到30中存在能整除31的整数,那么2到6中必然也存在能整除31的整数,所以检查大于6的整数只是浪费资源。
    利用这一性质,我们可以将检验范围从2到x-1缩小至2到xsqrt{x},算法的复杂度也就改良到了O(xsqrt{x})。比如x=1000000时,xsqrt{x}=1000,此时该算法快了1000倍。

    bool isprime(int x)
    {
    	if(x==2) return true;
    	if( (x<2) || !(x&1) ) return false;//x<2或x为偶数
    	int i = 3;
    	while( i<=sqrt(x) )
    	{
    		if( x%i==0 ) return false;
    		i += 2;
    	}
    	return true;
    }
    
    4. 埃拉托色尼筛选法

    有些时候,除了检验给定整数x是否为质数的函数之外,如果能事先准备出质数数列或质数表,就可以帮助我们更有效地求解质数的相关问题。
    埃拉托色尼筛选法(The Sieve of Eratosthenes)可以快速列举出给定范围内的所有质数,这个算法如下步骤生成质数表。
    埃拉托色尼筛选法

    1. 列举大于等于2的整数。
    2. 留下最小的整数2,删除所有2的倍数。
    3. 在剩下的整数中留下最小的3,删除所有3的倍数。
    4. 在剩下的整数中留下最小的5,删除所有5的倍数。
    5. 以下同理,留下仍未被删除的最小整数,删除该整数的倍数,一直循环到结束。

    以最小的4个质数为例,其求解过程如图。
    原始表:

    2 3 4 5 6 7 8 9 10
    11 12 13 14 15 16 17 18 19 20
    21 22 23 24 25 26 27 28 29 30
    31 32 33 34 35 36 37 38 39 40
    41 42 43 44 45 46 47 48 49 50
    51 52 53 54 55 56 57 58 59 60

    第一轮:

    2 3 4 5 6 7 8 9 10
    11 12 13 14 15 16 17 18 19 20
    21 22 23 24 25 26 27 28 29 30
    31 32 33 34 35 36 37 38 39 40
    41 42 43 44 45 46 47 48 49 50
    51 52 53 54 55 56 57 58 59 60

    第二轮:

    2 3 4 5 6 7 8 9 10
    11 12 13 14 15 16 17 18 19 20
    21 22 23 24 25 26 27 28 29 30
    31 32 33 34 35 36 37 38 39 40
    41 42 43 44 45 46 47 48 49 50
    51 52 53 54 55 56 57 58 59 60

    第三轮:

    2 3 4 5 6 7 8 9 10
    11 12 13 14 15 16 17 18 19 20
    21 22 23 24 25 26 27 28 29 30
    31 32 33 34 35 36 37 38 39 40
    41 42 43 44 45 46 47 48 49 50
    51 52 53 54 55 56 57 58 59 60

    第四轮:

    2 3 4 5 6 7 8 9 10
    11 12 13 14 15 16 17 18 19 20
    21 22 23 24 25 26 27 28 29 30
    31 32 33 34 35 36 37 38 39 40
    41 42 43 44 45 46 47 48 49 50
    51 52 53 54 55 56 57 58 59 60

    埃拉托色尼筛选法核心代码:

    #define MAX 1000000
    bool isprime[MAX];
    //bool型数组isprime表示质数表,
    //isprime[x]为true表示x是质数,为false表示x是合数。
    void eratos(int n)
    {
    	int i,j;
    	for(i=0;i<=n;++i)//列举整数作为候选的质数
    		isprime[i] = true;
    	isprime[0] = isprime[1] = false;//0和1不是质数,所以删除它们
    	for(i=2;i<=sqrt(n);++i)//留下i,删除i的倍数
    		if( isprime[i] )
    		{
    			j = i+i;
    			while( j<=n )
    			{
    				isprime[j] = false;
    				j = j+i;
    			}
    		}
    }
    

    埃拉托色尼筛选法需要占用一部分内存空间(与待检验整数的最大值N成正比),但其复杂度只有O(N log log N)。

    其实可以再优化一下代码:

    #define MAX 1000000
    bool isprime[MAX];
    //bool型数组isprime表示质数表,
    //isprime[x]为true表示x是质数,为false表示x是合数。
    void eratos(int n)
    {
    	int i,j;
    	for(i=0;i<=n;++i)//列举整数作为候选的质数
    		isprime[i] = true;
    	isprime[0] = isprime[1] = false;//0和1不是质数,所以删除它们
    	for(i=2;i<=sqrt(n);++i)//留下i,删除i的倍数
    		if( isprime[i] )
    		{
    			j = i*i;	//这边其实从i*i开始就可以了
    			while( j<=n )
    			{
    				isprime[j] = false;
    				j = j+i;
    			}
    		}
    }
    

    但其实你自己手动模拟一遍就会发现,埃拉托色尼筛选法不足之处也很明显,很多数被处理了不止1遍,比如6,在素数为2的时候处理1次,为3时候又标记一次,因此又造成了比较大的不必要处理。因此,下面引出线性筛法(欧拉筛法)求质数。

    5. 线性筛法(欧拉筛法)

    思路:
    筛的过程中要保证两点:
    1、合数一定被删掉了
    2、每个数都没有被重复地删掉

    那代码如何实现呢?(我们结合下面的代码分析)
    首先,明确一个条件,任何合数都能表示成一系列素数的积。
    其次,不管 i 是否是素数,都会执行到第18行——for(int j=0;j<tot;++j)处。
    这时,我们会遇到两种情况:

    1. 如果 i 是素数的话,那简单,一个大的素数 i 乘以一个不大于 i 的素数ans[j],这样筛除的数跟之前的是不会重复的。筛出的数都是 N=p1p2N=p_1*p_2 的形式, p1p_1p2p_2之间不相等。
    2. 如果 i 是合数的话,此时 i 可以表示成递增素数相乘 i=p1p2...pni=p_1*p_2*...*p_n , pip_i都是素数(2<=i<=n), pi&lt;=pj(i&lt;=j)p_i&lt;=p_j ( i&lt;=j ),其中p1p_1是最小的素数。而且满足条件的 i 有且只有一个,所以不会重复删除。

    再次,根据第23行——if( i%ans[j] == 0 )处的定义,当最小的素数p1等于ans[j]的时候,筛除就终止了,也就是说,只能筛出不大于最小素数p1的质数的i倍的数。即保证每个合数只会被它的最小质因数筛去,从而不重复筛选。

    线性筛法核心代码:
    #define MAX 1000000
    int ans[MAX];//记录哪些数字是素数,即ans[MAX]是一个素数集合
    bool valid[MAX];
    //数组valid[i]记录i是否为素数。初始所有的valid[i]为true。
    //线性筛选完之后valid[i]=ture的就是素数。
    void LinearPrime(int n)
    {
    	int tot = 0;
    	memset(valid,true,sizeof(valid));//初始所有的valid[i]为true
    	for(int i=2;i<=n;++i)//对于2到n中的每一个i
    	{
    		if( valid[i] )//如果i是素数
    		{
    			ans[tot] = i;//那么就将i这个素数加入素数集合ans[MAX]中
    			tot++;
    		}
    		for(int j=0;j<tot;++j)//对于当前素数集合中每一个元素ans[j]
    		{
    			if( i*ans[j]>n ) break;//若i*ans[j]>n,结束循环
    			//(因为确定素数的倍数超过讨论范围,没必要进行下一步了)
    			valid[i*ans[j]] = false;//将确定素数的倍数记录为合数
    			if( i%ans[j]==0 ) break;//保证每个合数只会被它的最小质因数筛去
    		}
    	}
    }
    
  • 相关阅读:
    DFS复习
    二叉搜索树专题
    二叉树路径问题
    二叉树LCA--leetcode236题
    二叉树创建与前、中、后序遍历
    leetCode--n数之和--哈希表/双指针
    leetCode--单词接龙--BFS
    vue-router简单实现
    Promise的简单实现
    闭包&作用域链&let
  • 原文地址:https://www.cnblogs.com/yuzilan/p/10626073.html
Copyright © 2011-2022 走看看