我们该如何判断一个数n是不是为素数呢?
方法一:遍历
方法二:埃氏筛法
方法三:线性筛(欧拉筛)
对于一个正整数n,我们可以从2到n-1遍历一遍,检查它是不是有除1和自身外的因子,进而判断它是不是素数。
int ay(int n){
for(int i = 2;i < n;i ++){ //i++就是i += 1
if(n % i == 0) return 0;
}
return 1;
}
可是这种方法看起来不是很聪明哦......简单思考一下,我们其实可以做出如下优化:
对于一个合数n,我们可以知道那些非1和n的因数都是成双成对出现的,它们的边界线是根号n,即对于<=根号n的每一个因数,我们都可以在另一边找到它的cp(流下了寡王的泪水)所以我们其实没必要遍历所有的数对不对?逮住一对cp中的一只就可以hhh
也就是说,我们从2遍历到根号n就OK啦
int ay(int n){
for(int i = 2; i * i < n; i ++){
if(n % i == 0) return 0;
}
return 1;
}//我不会告诉你ay是什么的hhh
同样,我们枚举因数也可以用这个方法——抓住每对cp其中的一只。
这种方法的复杂度分别为O(n)与O(n^1/2),看起来还是很不错的,但是当我们要判断很多很多整数的时候,这样的方法所要耗费的时间是很恐怖的,这就需要我们用更加优秀的方法来解决问题。
显然,我们知道对于一个素数,它的倍数必为合数。
利用这个想法,我们可以从2开始,先找到一个素数,然后砍掉该素数所有的倍数。
找到2,砍掉 4,6,8,10...
找到3,砍掉 6,9,12,15...
............
找到哪里为止呢?由前面“cp”的知识我们知道,找到2到根号n中的素数就可以了,一番砍砍砍之后,最后留下的就会全是素数。(YAHOO!!)
#include
#include
#define ll long long
#define maxn 1000001
using namespace std;
int pocket[maxn];
bool num[maxn];
int sieve(int n){
memset(num, true, sizeof num); //memset的作用是把num数组的所有成员都变成true
int cnt = 0;
num[0] = num[1] = false;
for(int i = 2; i <= n; i ++){
if(num[i]){
pocket[cnt++] = i;
for(int j = 2 * i; j <= n; j += i)
num[j] = false;
}
}
return cnt;
}
int main(){
int n;
cin >> n;
cout << sieve(n) << endl;
return 0;
} //这段代码用来查询1到n之间有多少质数
#include
#include
using namespace std;
const int maxn = 10000001;
bool num[maxn];
void sieve(){
memset(num, true, sizeof num);
int cnt = 0;
num[0] = num[1] = false;
for(int i = 2; i <= maxn / i; i ++){
if(num[i]){
for(int j = 2 * i; j <= maxn; j += i){
num[j] = false;
}
}
}
}
int check(int x){
if(num[x])return 1;
else return 0;
}
int main(){
int n, x;
sieve();
cin >> n;
while(n --){
scanf("%d", &x);
if(check(x))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
return 0;
}/*这段代码可以判断1e7之内的数是否为素数,你可以看到仅仅是判断素数的时候,
sieve函数中第一个for循环在maxn^(1/2)时即可以结束*/
埃氏筛的算法复杂度为O(nlog(logn)),在数据量大的时候,秒杀了我们考虑的第一种朴素算法。
不对啊,埃氏筛看起来已经很好了,还有什么能改进的吗?
我们来复盘一下过程:
找到2,砍掉4,6,8,10,12...
找到3,砍掉3,6,9,12,15...
找到5,砍掉10,15...
我们发现,在我们砍树数的时候,6、10、12、15这些数字被重复砍了几次,而重复性的工作恰恰拖慢了我们的算法。我们不禁要问,有什么方法可以避免重复砍数嘛?
答案是肯定的——线性筛法
从欧拉那里我们知道了一个事实,即对于任一合数n,有n=maxfactor*p.
其中maxfactor为n的最大因数(该因数的值显然唯一),p为n的最小质因数
更进一步,算数基本定理告诉我们,任一大于1的正整数都可以分解成有限个质数的乘积
所以办法便有了:我们从2开始拿着已有的素数,依次把它们当做最小素因数,分别与后面的素数乘起来得到合数(其实没必要一定是后面的素数,但你懂的,这样更快),砍掉这些合数,从而得到所有的素数。
这种方法的好处是,我们是根据一个合数的最小质因数找到并且砍掉它的,因此对于10、12这种含有多个质因子的合数,我们只会按最小质因数2筛取一次,避免了重复工作。(好耶!!)
代码实现:
#include
#include
const int maxn = 1000001;
using namespace std;
int pocket[maxn];
bool num[maxn];
int sieve(int N){
memset(num, true, sizeof num);
int cnt = 0;
num[0] = num[1] = false;
for(int i = 2; i <= N; i ++){
if(num[i])pocket[++cnt] = i;
for(int j = 1; j <= cnt && pocket[j] * i <= N; j ++){
num[i * pocket[j]] = false;
if(i % pocket[j] == 0)break; //如果筛过了就跑路继续
}
}
return cnt;
}
int main(){
int N;
cin >> N;
cout << sieve(N) << endl;
return 0;
}
线性筛把时间复杂度做到了 O(n),比埃氏筛更加优秀(bingo!)