彻底理解线性筛选法
问题:求所有小于等于n(n比较大)的所有素数
首先可能最容易想到的是写一个函数来判断它是不是素数,但是对于求比n小的整数就显得时间复杂度太高了,一般解这种问题会采用筛选法...
埃氏筛选法
思想是,使用一个位数组is_prime
保存每一个数是否是素数,然后每次找到一个素数x,就把这个素数的i倍,i满足(x * i <= n)踢掉,即将is_prime设置为true(默认是false)。这样找到一个数后,再找的下一个is_prime = false的数,一定是素数(因为如果不是,它就会被某个比它小的数因子踢出掉)
线性筛选法
埃氏筛选法的效率已经不低了,但是会有很多的重复计算,即一个数会被多次踢出,浪费了计算时间。对于任何一个合数z,一定存在质数x,使得:
z = x * y (y 不为 1)
但是这样的x不唯一,比如6=23=32,这里x = 2或者3。所以我们现在要找一个办法,让每一个合数只能被踢出一次而不是多次。这时注意到x的最小值是一定的,所以我们可以用每一个合数的最小质因子来踢出。
即:
10由2踢出
9由3踢出
77由7踢出
这样就实现了一个合数被一个最小质数踢出,大大加快了运算效率。
那么在程序实现时怎么做呢?
我们可以在遍历2-n时,用当前已经找到的每一个质数去乘以这个当前数i,在这些质数眼里(不是当前遍历的数!!),这样就相当于:
质数2分别与2,3,4,5,6,7,8,9...,踢出了(4,6,8,10,12,14,16,18)
质数3分别与3,4,5,6,7,8,9...踢出了(9,12....)
质数5分别与5,6,7,8,9,10,11,12...,踢出了(25,30...)
质数7分别与3,4,5,6,7,8,9...踢出了(21,28....)
这样看起来还是有不少重复啊,不要急,目前这种方式相当于埃氏筛选法,这里最核心的一步是,如果当前数i的因子已经有了某个质数的话,那么当前数i就不再继续与下一个质数相乘了。
什么意思?
就是说,当i = 6时(遍历到6这个合数时),我先把2 * 6 = 12踢掉,当我想继续再想把3(第二个质数) * 6 = 18页踢掉时,这时候等一等!由于:
6 % 2 == 0
这个时候就不要继续往下走了,因为将要计算的下一个要踢出的数的质最小因子一定是刚刚那个质数(2)!,为什么?因为当前数都可以整除刚刚那个质数了!
这样就保证了一个合数只能被一次踢出,比如:
100 一定是在i = 50被踢掉的
70 一定是在i = 35被踢掉的
45 一定是在i = 15被踢掉的
证明:
首先证明合数t一定会被踢掉:
对于t,一定存在最小质数a,所以当遍历到i = t / a 时,踢掉t/a * a = t
然后证明t只会被踢一次
使用反证法,设i = x,i = y时踢出t,这里x < y,a、b是质数且
a是最小质因子(已证明这个一定存在),则t = x * a
t = y * b
由于任意一个合数都可以表示为am*bn.....其中a,b,c...是质数,如45=3^2*5
所以当b!=a时,b一定大于a,这个时候循环已经超过a了(a是最小质因子),而45已经含有a这个因子,所以有t % a == 0得,此时循环已经break,因此不会重复计算任意合数。
代码
埃氏筛选法:
#include <iostream>
#include <vector>
using namespace std;
vector<int> primes;
bool *is_prime;
void get_all_prime(int n){
is_prime = new bool[n + 1];
for(int i = 2; i <= n; i++){
if(!is_prime[i]){
primes.push_back(i);
for(int j = i * 2; j <= n; j = j + i){
is_prime[j] = true;
}
}
}
}
int main(){
get_all_prime(1000000000);
//int len = primes.size();
//for(int i = 0; i < len; i++){
// cout << primes[i] << endl;
//}
cout << primes.size();
return 0;
}
计算[2,1000000000]素数花了26s
不使用vector花了24s
线性筛选法(使用vector)
#include <iostream>
#include <vector>
using namespace std;
vector<int> primes;
bool *is_prime;
void get_all_prime(int n){
is_prime = new bool[n + 1];
for(int i = 2; i <= n; i++){
if(!is_prime[i]){
primes.push_back(i);
}
int size = primes.size();
for(int j = 0; j < size && primes[j] * i <= n; j++){
is_prime[primes[j] * i] = true;
if(i % primes[j] == 0)
break;
}
}
}
int main(){
get_all_prime(1000000000);
//int len = primes.size();
//for(int i = 0; i < len; i++){
// cout << primes[i] << endl;
//}
cout << primes.size();
return 0;
}
计算[2,1000000000]素数花了19s
线性筛选法(不使用vector)
#include <iostream>
#include <vector>
#define MAX_CAP 100000000
using namespace std;
bool *is_prime;
int data[MAX_CAP];
int size = 0;
void get_all_prime(int n){
if(n == 2){
data[0] = 2;
size = 1;
return;
}
if(n == 3){
data[0] = 2;
data[1] = 3;
size = 2;
return;
}
is_prime = new bool[n + 1];
for(int i = 2; i <= n; i++){
if(!is_prime[i]){
data[size++] = i;
}
for(int j = 0; j < size && data[j] * i <= n; j++){
is_prime[data[j] * i] = true;
if(i % data[j] == 0)
break;
}
}
}
int main(){
get_all_prime(1000000000);
cout << size;
return 0;
}
计算[2,1000000000]素数花了9s。。。