数论(一)素数的判定及其优化

今天来填一填坑了,虽然讲该内容显得不那么紧要。但是为了知识的完整性,我希望能够去进行补充吧。

其实任何人也还是需要经过这个过程的。hhhhhh…

参考书籍:
《算法笔记》
《算法竞赛入门经典-刘汝佳》

参考资料以及链接:
素数的讲解(希望能看看这篇博客真的是太棒了)

相应题目:

素数,质因子分解 acwing,pat,leetcode
acwing AcWing 866. 试除法判定质数
acwing 867. 分解质因数
acwing 868. 筛质数
acwing AcWing 869. 试除法求约数
leetcode 204. 计数质数
pat-A-1059 1059 Prime Factors (25分)
pat-B-1007 1007 素数对猜想 (20分)

从数论开始谈及,希望不要因为数论的两个字而唬住同学,它仅仅是一个代名词而已。
数论就从素数开始吧,我第一次认识素数,并且写代码应该还是大一的时候,那么时候仅仅是对该知识点的记忆吧。第一次的深刻理解(现在看来当时学得可真差)可能还是翁凯老师的c语言程序设计吧。

素数从百度百科给出如下的定义:质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
根据这个定义,很容易就可以写出代码来判断某一个数是否为素数了。
代码的模板如下(哈哈哈,似乎这个是最通用的吧):

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

这是稍微已经进行了优化的写法,算法的复杂度在O(sqrt(n)),看起来不错呀,好像也还可以呀。但是如果为了过leetcode204,似乎是不可以的。如果是为了过pat的乙级7和甲级59,普通的素数判定基本就可以搞定了。我认为leetcode204是考察**埃氏筛法,或者再往前一步,考察的是线性筛法**。

既然学无止境,就要往前去跨一步。计算机是笨拙的,我们人类呢,希望操作的过程能够不重复,如果可以的话,更希望能用到现成的东西。

说到埃氏筛法,不要恐惧,仅仅是一个优化的过程。它的思想和暴力判断素数的思想恰恰相反。暴力判断素数是逐个判断,并选取出素数。但是埃氏呢,他是剔除掉非素数,那么剩下的就是素数啦。注意:优化就是在剔除的过程中去进行剔除的。
埃氏筛法思想:剔除掉素数的倍数
比如现在我要去获得100以内的素数
2的倍数是4,6,8,10,12…
3的倍数是6,9,12,15

这样的效率高了很多
时间复杂度为O(nlnlnn)
似乎优化了很多……
代码的模版如下

#include 
using namespace std;
const int N=1000010;
int primes[N];//质数表
bool st[N];//判断是否为质数
int cnt;//计算质数的个数

//埃氏筛法-->线性筛法
//在10e7 线性筛法比埃氏筛法更快,快一倍 
void linear_get_primes(int n){
	for(int i=2;i<=n;i++){
		//如果该数是素数的话,就把该数放到数表里面去 
		if(!st[i]){
			primes[cnt++]=i;
		}
		//区别于埃及筛法(去除素数的倍数),
			//线性筛法是: 从小到大枚举所有的质数 
		for(int j=0;primes[j]<=n/i;j++){
				st[primes[j]*i]=true;//筛除该数 
				if(i%primes[j]==0){
					break;  //primes[j]一定是i的最小质因子
					//1.i%primes[j]==0:primes[j]一定是i的最小质因子,primes[j]也一定是primes[j]*i的最小质因子
					//2.i%primes[j]!=0:primes[j]也一定是primes[j]*i的最小质因子
				} 
		} 
	}
}
int main(){
    int n;
    cin>>n;
    linear_get_primes(n);
    cout<<cnt;
    return 0;
}

但是不知道同学有没有发现埃氏筛法有一个小缺陷,相信大家都发现了。在剔除倍数的时候,好像有重复了。怎么个重复法呢?
那就举举例子吧……,在上述的埃氏晒法中
2的倍数为2,4,6,8…30…60
3的倍数为6,9,12,15…30…60
5的倍数为10,15,20…60
似乎我们在剔除60的时候重复遇到了三次,数字越大,其实出现的次数是更多的。假设这个数n的范围之内,假设n之内素数存在n/ln n个质数(由质数定理可得),那个n就会连续被剔除的n/ln n次。其中复杂度为**O(nlnlnn)**
那这样又存在了优化呀。
这次的优化叫做线性筛法,又叫欧拉筛法。
虽然说了思想,但是操作起来好像还是不那么容易呀。
虽然2,3,5都重复遇到了30。难道我在确定一个素数的过程中还要一直往后找?是不用的,走好当下就够了,优化好当下就可以了。
详情请看看上面的博客,一定要看,上面的博客可能是我见过最通俗易懂的博客了。

#include 
using namespace std;
const int N=1000010;
int primes[N];//质数表
bool st[N];//判断是否为质数
int cnt;//计算质数的个数


//埃氏筛法-->线性筛法
//在10e7 线性筛法比埃氏筛法更快,快一倍 
void linear_get_primes(int n){
    for(int i=2;i<=n;i++){
        //如果该数是素数的话,就把该数放到数表里面去 
        if(!st[i]){
            primes[cnt++]=i;
        }
        //区别于埃及筛法(去除素数的倍数),
            //线性筛法是: 从小到大枚举所有的质数 
        for(int j=0;primes[j]<=n/i;j++){
                st[primes[j]*i]=true;//筛除该数 
                if(i%primes[j]==0){
                    break;  //primes[j]一定是i的最小质因子
                    //1.i%primes[j]==0:primes[j]一定是i的最小质因子,primes[j]也一定是primes[j]*i的最小质因子
                    //2.i%primes[j]!=0:primes[j]也一定是primes[j]*i的最小质因子
                } 
        } 
    }
}
int main(){
    int n;
    cin>>n;
    get_primes(n);
    cout<<cnt;
    return 0;
}

素数的基本内容就讲解完了
回顾一下内容的基本讲解如下:暴力->埃氏筛法->线性筛除。请读者尽量在脑子里过一下内容。
接下来是例题讲解:

204. 计数质数
数论(一)素数的判定及其优化_第1张图片

class Solution {
public:
    int countPrimes(int n) {
        //要使用埃氏筛法,或者线性筛法,不然数据集过不去
        vector<int> primes;
        vector<bool> isnp(n+1);//构造一个素数表,is no prime
        for(int i=2;i<n;i++){
            if(!isnp[i]) primes.push_back(i);
            for(auto p:primes){
                if(p*i>n) break;
                isnp[p*i]=true;
                if(i%p==0) break;
            }
        }
        return primes.size();
    }
};

ps:pat这两题都是比较简单的,基本上是在格式上进行了考察。在数据上,不会像leetcode那样卡得那么死。

pat-A-1059
pat-A-1059 Prime Factors (25分)

pat-B-1007
pat-A-1059 素数对猜想 (20分)

你可能感兴趣的:(笔记)