您的位置:首页 > 其它

求素数的四种方法及证明

2018-03-26 20:18 120 查看
1. 定义法筛素数
    众所周知,素数的定义是只能被1和它本身整除的素数。那么我们只要去找到一个数a,然后枚举从2开始小于这个数的数,把它定义为b,当b能被a整除时,通过b是否小于a来判断a是否是素数。#include<iostream>
using namespace std;
const int MAXN = 1e6 + 5;

int main(){
for(int i = 2; i < MAXN; i++){
int j;
for(j = 2; j <= i; j++){
if(i % j == 0){
break;
}
}
if(j == i){
cout << i << " ";
}
}
return 0;
}    我们很容易就能看出这个方法的复杂度是O(n²),很明显不够我们平常使用时的速度。所以需要一种更快的方法。
2. 定义法筛素数(平方根优化版)
    我们在用第一种方法时会发现多了一些重复步骤,如果一个大于1的自然数a能被两个数b,c(b <= c)乘积得到,那么一定会有b∈[1, sqrt(a)],c∈[sqrt(a), a],所以b和c一一对应,只要枚举b就能得到a的所有因子。
#include<iostream>
#include<cmath>
using namespace std;
const int MAXN = 1e6 + 5;

int main(){
for(int i = 2; i < MAXN; i++){
int j, m = sqrt(i);
for(j = 2; j <= m; j++){
if(i % j == 0){
break;
}
}
if(j > m){
cout << i << " ";
}
}
return 0;
}    通过把第二层循环减为sqrt(a),我们很轻易出此算法的复杂度为O(n * sqrt(n)),复杂度依然比较大,同时基于定义上的筛法这种也几乎是最优化的方案,我们再想快一些,就要从理论方案上改变。
3. 一般线性筛素数
    没有百度到一个满意的证明法,所以博主自己证明了一下,如果有错误还请评论指出。

    我证明方法基于每个合数都能拆解为几个质数的积。比如m = a1 * a2 * a3 * ... * an,设a2 * a3 * ... * an = b,那么m = a1 * b,即每个合数m都能拆解为一个质数a1和一个自然数的积b。那么我们只要枚举每一个质数a1和自然数b,就能筛掉所有的合数。当然,这么做我们会有两个问题,第一,a1 * b能不能组成除合数以外的数,这涉及到m = a1 * b的充分必要性。第二,我们要如何去找到a1,在筛素数的方法中要先找到素数,直观上来说这是不可能的。我会在接下来一一证明。

    关于第一个问题,我们要证明a1 * b = m,首先自然数分为质数和合数,我们要证明这里的m不会是质数就能证明它的正确性,设质数m = m * 1,这是质数m的唯一一种分解方法,其中a1是质数,那么a1 ≠ 1,所有只有a1= m且b = 1时,a1 * b = 质数m,所以我们在枚举过程中让b从2开始,就能得到所有的合数m。
    剩下第二个问题,如何找到质数。首先,素数m一定不会被筛掉,而合数m则会被比它小的a1筛掉,我们把a1从2到最大值MAX依次枚举,那么我们在枚举任意数时,如果是合数a1,它一定会被之前的质数a1筛掉,那么没有被筛掉的一定是质数。也就是说在枚举a1的过程中,只要没有被筛掉的数就是质数a1。#include<iostream>
#include<cstring>
using namespace std;
const int MAXN = 1e6 + 5;

bool prime[MAXN];

int main(){
memset(prime, 0, sizeof(prime));
prime[0] = prime[1] = true;
for(int i = 2; i < MAXN; i++){
if(prime[i] == false){
for(int j = 2; j * i < MAXN; j++){
prime[i * j] = true;
}
}
}
for(int i = 0; i < MAXN; i++){
if(prime[i] == false){
cout << i << " ";
}
}
return 0;
}
    其实在核心代码上,第一层还是运行了n次,但第二层却只运行了n / i次。这个优化量是非常可观的,但是还是有一些可以优化的方案。
    比如我们每次都会把b从2开始,但是当b∈[2, i),所有的a1 * b都已经被筛过了。如果b是质数,那么b < a1,在i = a1之前已经有i = b,有关b的所有倍数都被筛掉,其中当然也包括b * a1。当b是合数时,设b = a1' * b',则m = a1' * a1 * b',其中a1' < a1,设b" = a1 * b',那m = 质数a1' * b",故在a1之前,m已经被a1'筛掉了。所以b∈[2, i)时重复的操作。
    当然这还有另一种解释,当合数m = a1 * a2 * a3 * ... * an,如果a数列从小向大排列,那么此时a2 * a3 * ... * an = b >= a1,所以m = a1 * b也可以表述为任一合数m都可以拆解为一个质数a1和一个不小于a1的自然数b。这时我们要证明它的逆命题a1 * b = m,根据质数的定义,不存在可以拆解为一个质数和一个大于质数的数的积的质数,所以a1 * b = 合数m。所以b >= a1,那么j就大于等于i。

    那如果让j从i开始,我们就没必要把i筛到MAX,因为在sqrt(MAX)后,j的最小值都没办法筛掉任何数。所以我们把i从2到sqrt(MAX),j从i开始。#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
const int MAXN = 1e6 + 5;

bool prime[MAXN];

int main(){
memset(prime, 0, sizeof(prime));
prime[0] = prime[1] = true;
int m = sqrt(MAXN);
for(int i = 2; i <= m; i++){
if(prime[i] == false){
for(int j = i; j * i < MAXN; j++){
prime[i * j] = true;
}
}
}
for(int i = 0; i < MAXN; i++){
if(prime[i] == false){
cout << i << " ";
}
}
return 0;
}
    但此时依然不是线性筛法,因为我们基于m = a1 * a2 * a3 * ... * an,使其能组成多少m = a1 * b(a1 <= b),m就被筛掉多少次,这种重复性就是时间复杂度增长的来源。
4. 欧拉筛法求素数
    欧拉筛法是也会是我自己证明的,如有错误还请指出。
    我们在线性筛m = a1 * b时,外层循环是枚举a1,而欧拉筛法是枚举b,然后枚举小于b的质数a1。但是我们在筛的过程中,只要保证a1是m最小的质因子,那每个质数就只被筛一遍,复杂度就是O(n)。
    我们枚举b后,在枚举小于b的质数a1时,如果b % a1 = 0,那么b = k * a1。这时如果继续向后枚举a1'(a1 < a1'),那你所得的m = a1' * (a1 * b),a1'就不会是最小的m最小的质因子,所以当b % a1 = 0时,直接跳出循环就行了。
    这时就出现一个问题,就是我们能保证筛掉的a1一定不是最小质因子,但是为什么所有不是最小质因子的a1一定被筛完了。

     我们先假设m被不是它最小质因数a1'筛掉,如果它最小质因数是a1,则m = a1 * a1' * b。设b' = a1 * b,则我们在枚举b'时,一定会有b' * a1被枚举且比b' * a1'先枚举到。那么a1'就一定会被筛掉,所以不存在被不是最小质因数筛掉的合数。#include<iostream>
#include<cstring>
using namespace std;
const int MAXN = 1e6 + 5;

int isPrime[MAXN], maxPrime;
bool prime[MAXN];

int main(){
me
836d
mset(prime, 0, sizeof(prime));
maxPrime = 0;
prime[0] = prime[1] = true;
for(int i = 0; i < MAXN; i++){
if(prime[i] == false){
isPrime[maxPrime++] = i;
}
for(int j = 0; j < maxPrime && i * isPrime[j] < MAXN; j++){
prime[i * isPrime[j]] = true;
if(i % isPrime[j] == 0){
break;
}
}
}
for(int i = 0; i < maxPrime; i++){
cout << isPrime[i] << " ";
}
return 0;
}    欧拉筛法是欧拉函数删去phi数组后的代码,如果对欧拉函数感兴趣,博主以后会写一篇欧拉函数的单独讲解。    
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: