一.奇数求余法,列出所有的奇数,然后在判断这些奇数有没有能被整除的小于它自身的奇数,有就不是质数;
#include
using std::cout;
using std::endl;
int main() {
cout << "2" << endl;
int digit;
int divisor;
for (digit = 3; digit <= 15; digit += 2)
{
for(divisor= 3;divisor < digit;divisor += 2)
{
if(digit%divisor==0)
{
break;
}
}
if(digit==divisor)
{
cout<<digit<<endl;
}
}
}
return 0;
}
这个的思维就是用数组做标记,数组的下标为数字,数组的内容为标记。1为质数,0为合数
标记的原理就是用最开始的几个质数标记后面的非素数,因为一个数n以内所有的合数都是2,3,5,7~n^(1/2)这些数的倍数。
而不同筛法的优劣就是看标记的重复度。
1.第一种表示
#include
int main() {
int n = 15;
int mark[16] = {
1, 1, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
};
int c;
int j;
for (c = 2; c * c <= n; c++) {
if(mark[c]!=1){
for(j = 2;j<=n/c;j++){
mark[c * j]=1;
}
}
}
for(c = 2;c<= n;c++)
{
if(mark[c] !=1){
printf("%d\n",c);
}
}
return 0;
}
2.第二种表示
1 int prime[MAXN];//素数数组
2 bool isprime[MAXN + 10];//is_pri[i]表示i是素数
3 //返回n以内素数的个数
4 int sieve(int n)
5 {
6 int p = 0;//素数个数计数器
7 for (int i = 0; i <= n; i++)
8 is_prime[i] = true;
9 is_prime[0] = is_prime[1] = false;//首先标记0和1不是素数
10 is_prime[2] = true;//标记2是素数
11
12 for (int i = 2; i <= sqrt(n); i++)
13 {
14 if (is_prime[i]) //如果i是素数
15 {
16 prime[++p] = i;//将素数放进素数表
17 for (int j = i * i; j <= n; j += i)//所有i的倍数都不是素数
18 is_prime[j] = false;
19 }
20 }
21 return p;
22 }
第二种的第二种表示更有效率
对于n以内的数来说,如果n为合数,那么它的最小公因数c(除了1)一定在下面这个范围里:
1<= c<=n^(1/2)
那么我只要在这个范围内求出所有的倍数,则这些倍数得到的值都是合数(所有的合数都是由2、3、5~n^(1/2)中的一个或两个乘一个数得到的)
第一种就是将2~n^(1/2)
中的每一个数x乘以2 ~n/x,可以看到这中间是有重复的
当第一行的数取值为2,第二行的倍数取值为3,和
第一行的数取值为3,第二行的倍数取值为2时,两个得到的是一个合数6
那么第二种的第二行的倍数从x直接开始就是为了避免这种重复,但是并不能完全避免重复,比如:n=16,第一行i取值2,j为16与第一行i取值4,第二行j取值为16,就会重复。
内部的循环这里为什么j=ii,而不从2开始呢?
因为这里是求的i的倍数,也就是(2~n^1/2)里所有质数的倍数,那么当j=(i-1)(i-1)时迭代时后面j=(i-1)i,这样如果当j=ii时不用再求前面的倍数,就不会有重复
上述几种方法都存在一个问题,那就是会重复标定一个合数,这样这个算法的时间复杂度就变大了。比如第二种的第二个对6这个合数会标记两次,2x3,3x2。第二种第二个虽然可以避免一些重复,但还是有一些重复,比如对于18这个合数,在2x9和3x6这个两个地方还是会有重复。
那么有没有一种算法可以只对一个合数标记一次呢? 答案是有的。下述即为该算法的思想
在编程时,M通过逐一列举,p通过列举素数表得到。
图上第三大条的一三两个条件互为必要条件p为N的最小素因子,那么p肯定小于等于M的最小素因子。
这里面还有一个定理是一个合数M一定能被表示为素数幂连乘,即如下:
12=2^2 *3
这个定理也可以说明上述定理
为什么前面几种算法都会有重复的呢?
因为前面都是从素数出发,把素数的倍数,来标记合数,但是显而易见的是一个合数它是有可能通过不同的素数得到,比如18,可以是素数2的9倍,或者素数3的6倍。那么这就会重复
如何保证不重复呢?
由上面的定理可知,我们只要知道每个合数都可以通过最大因子与其相对应的最小素因子相乘确定,那么只要确定最大因子再确定最小因子将两个相乘,得到的就是唯一的合数
唯一需要解决的就是我怎么确定两个相乘得到的数一定是唯一的(唯一就不会重复),就是:p为小于或等于M的最小素因数(只有这样,p才是相乘得到的N的最小素因数,如果不是这样就会有两个其它的数相乘也得到这个N(这里不是说合数N只有p*M得到,而是通过相同的算法也可以在其它步骤通过另外两个数得到N),而有了以上算法规则那么就得到N的数就只有一组,这样就只有一个步骤可以标定N),那有人会说这样得到的合数是全部吗,怎么知道有没有漏掉,其实我们根据那个上面那个合数法则(一个合数M一定能被表示为素数幂连乘),我们可以把这个式都表达成一个最小的素因子与一个最大的因子相乘,而我们通过计算的合数就是通过相同的规则算出来然后进行标定的,那么肯定能计算出所有的合数。
说的可能有点蒙,举个例子:
还是以合数18
它可以通过29或36得到,
如果没有上述规则算法,那就会和第二种一样,有两个步骤两组数得到同一合数,重复标定。
如果加了上述算法,那么3*6就不满足,因为3不是6的最小素因子,自然也就不是18的最小素因子,就不会计算它来标定,
这里当36时得到的合数在线性筛法下在29时会得到,也就是超过这个算法得到的合数都会在这个算法里,变相也说明了这个算法已经将所有的合数包含了进来。
这里的M就是6,M没有限制都可以,M一确定,p的范围就确定了,p*M也就是唯一的;
代码如下:
#define maxint prime[max+5];//这里多加5个主要是防止越界
int initial()
{
for(int i=2; i <= max; ++i)//列举M
{
if(!prime[i]) prime[++prime[0]] = i;/*prime里的元素为0,即为素数时,
使用prime[0]为计数位,将prime数组从下标1开始覆盖为素数的大小,即将用
来标记0,1代表是否为素数的数组覆盖为,第几个素数为多少的数组。
因为素数大小对0,1的覆盖总是在判断之后,即prime[0]总是小于i的所以下一
次判断prime是否为素数不会被覆盖,没有影响。*/
for(int j =1; j<= prime[0]; ++j)//列举p
{
if(prime[j]*i >= max) break;//当p*M大于N时,要标记的合数超出了范围
prime[prime[j]*i] =1;//标记大小为p*M的为合数
if(i%prime[j] == 0)break;/*当p为M的最小素因子时,退出,往后的p
就不是最小的素因子了,不是最小素因子得到的合数就会重复标定,比如合数12,
当p,m分别为2,6时p为最小素因子,这个时候p也为m的最小素因子,p就不能再
往后取3了,当取3时,3*2*2=12,3就不是n里最小的素因子,就会和后面的M取6,
p取2时的合数标定重复。 */
}
}
}