<p>acm<span style="font-family:宋体;">
acm中对素数的操作是极为频繁,对素数的操作并不难,但对于初学者的还是有一定的挑战,我们来对其进行分析对素数的判断在新手中最常见的一种办法,就是暴力求解,
bool isPrime=false;
for(int i=2;i*i<=n;++i)
if(n%i==0)
{
isPrime=true;
break;
}
这是一种非常常见的新生级算法,他在素数判断上的位置等价于冒泡排序选择排序在排序中的地位,而在排序上我们最终选择了堆排序,快排,分治等一系列nlogn级别的算法,那么在素数判断上我们该如何抉择?
那就是 筛选法,首先,我们先来确定一件众所周知的事情,任何数,当然0不算,而且还要是整数范围内的数,只要符合这以上几点,那么他就可以拆分为由几个相同或不相同的素数所乘,比如5=1*5,12=2*2*3,负数的话在加个-1,每个非0非1整数的绝对值都对应着一个质数序列,一一对应也就是满射,这说明了什么?判断一个数是不是质数你并不需要从2到sqrt(n)花费sqrt(n)的时间来计算,如果你明确了从1到n之间的所有质数,那么用质数诶个除就好了。
你问我如果n就是质数怎么办?是不是要把1到n之间的质数序列都遍历一边?当然不,只要遍历到某个质数>sqrt(n)就好了,为什么?都遍历到某个数a>sqrt(n)了,说明这个数n对应的质数序列里最小的质数,也就是他的最小质因数大于sqrt(n),这明显不合理,100可能有大于10的最小质因数吗?不可能,因为如果有的话,设这个最小质因数为i依据之前每个正整数对应一个质数序列的定理,100=i*(某个质数序列)因为i为最小质因数,所以“某个质因数序列”必然>=i 这就导致了等号左右不相等。
所以,一个比较好的素数判定算法是这样的。
int prime[numOfPrime];//一个现成的质数序列
bool isPrime=false;
for(int i=0;i<numOfPrime;++i)
{
if(prime[i]*prime[i]>n) break;
if(n%prime[i]==0) {isPrime=true;break;}
}
接下来,就谈到了利用筛选法对prime的初始化操作,不过其实相等简单,依旧是利用
最初的每个数与质数序列一一对应的定理,不过我们还要加一个相等显而易见到无脑的结论,那就是那些质数序列里的数一定比他们相乘所得的数小,这意味着什么呢?着意味着每一个合数包含的质数序列中的数都是在之前出现过的。
所以,代码如下
#include <stdio.h>
#include <string.h>
#include <math.h>
int primer[100001];//我们初始化了从1到200000之间的所有质数在primer中,当然,质数数量肯定不满200000个,而且质数除了2都是奇数,所以这里其实只要10000即可
bool vis[100001];//vis记载了我们对质数的判断,vis的大小决定了我们质数判定的范围,这里判定范围为1-200000,vis使用了10001大小的理由同上
int numOfPrimers;//记录了prime的数量
bool isPrimer(unsigned int i)//质数的判断
{
for (int k = 1; k <= numOfPrimers&&primer[k] <= sqrt((double)i); ++k)//有一点,我们的primer是从第一位开始记录而非第0位
{
if (i%primer[k] == 0)
return false;
}
return true;
}
int main()
{
memset(primer, 0, sizeof(primer));
memset(vis, 0, sizeof(vis));
//1-200000内质数判定
//2是特例,先写入
primer[1] = 2; numOfPrimers = 1;
for (unsigned int i = 0; i < 100001; ++i)if (vis[i] == false){
// vis[i]代表了i*2+3这个数是否被访问过,因为我们排除了所有偶数,所以vis[i]并不代表i;
primer[++numOfPrimers] = i * 2 + 3;
//将以后的能被i*2+3整除的数都访问一遍,代表i*2+3是这些数的质因子,那些被访问数都是合数
// k的初始值不是i*2+3是为了避免重复计算,如果k=i*2+3的话,会和别的质因子发生重复计算
// 比如21=3*7,21由vis[6]代表,3和7由vis[0]与vis[2]代表,vis[6]会被重复访问,而更改
//k为i*(2*i+6)则没有重复的危险
for (unsigned int k = i*(2 * i + 6) + 3; k < 100001; k += i * 2 + 3) vis[k] = true;
}
//printf("%d", numOfPrimers);
int T; unsigned int n;
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
if (isPrimer(n))
printf("yes\n");
else
printf("no\n");
}
return 0;
}
以上就是筛选法得到质数从而简化质数判定的方法,每一个vis[i]都代表一个奇数,vis[0]代表3,vis[1]代表5,当我们的i=0时,vis[0]的值为false
说明3没有被访问,3是质数,然后我们向后遍历,将因数中含有3的奇数访问一遍,在i=1之前,vis[3]代表的9,vis[6]代表的15都被加上了true,
代表不是质数,所以,当vis[i]==false时,就代表他是个质数,因为他无法被之前比他小的所有质数所除,他的质因数就为他本身,当然了,觉得这行代码
有点难受的同学不要着急,这是被ACM界的前辈优化过的代码,有非常好的效率,虽然看起来有那么一点点复杂,所以平常情况下我们可以牺牲一点效率
使用一下简易版本
#include <iostream>
#include <string.h>
using namespace std;
int primer[100001];
bool vis[200001];
int numOfPrime = 0;
void initPrimer()
{
memset(primer, 0, sizeof(primer));
memset(vis, false, sizeof(vis));
for (int i = 2; i <= 200000; i++)
if (vis[i] == false)
{
primer[++numOfPrime] = i;
for (int k = i; k <= 200000; k = k + i) vis[k] = true;
}
}
int main()
{
initPrimer();
cout << numOfPrime;
return 0;
}
是不是美观多了呢,平常就这样写写也没有多少关系,不过有一点很重要,大家注意到了primer与vis里的数据里吧,100001与200001,建议不要
使用数字,使用宏或者const int常量比较好,这是一种很符合规范的编程习惯
总结:
使用筛选法得到一定范围内的质数数据然后去判断素数是非常高效的,但是但是,依然有缺点,因为他的数据是非常依靠vis这个数组的
而数组的大小是有上限的0x7fffffffffff,也就是int的最大值,这意味着什么呢?这意味着在我们一般人电脑上,直接使用这个方法去判定从
1到某个longlong级别的数据之间的质数是有点困难的,对此,我们可能就需要借助我们最初的方法,遍历,虽然低效但是可靠,当超出了
我们能够判定的范围时,就需要结合使用这个遍历方法来继续判断