素数算法总结
1.关于素数算法(Eratosthenes筛法)的总结
素数的定义:
素数又叫质数(prime number),有无限个,只能能被1和自身整除的自然数.
由于这句话,在我们求给定范围N以内的素数的时候,我们通常采用的是下面这种算法:
素数算法1.0
#include <stdio.h> int main(void) { int n; /* 给定范围N */ char flag; /* 判定标志 */ int i, j; scanf("%d", &n); /*从2开始遍历到n以内的所有自然数, */ for (i = 2; i <= n; i++) { flag = 1; /* 初始化为1, 假设其为素数 */ /* *判断其能否被 从2开始到比其自身小1 的数整除, *如果能被整除, 则说明岂不是,反之则是. */ for (j = 2; j < i; j++) { if (i % j == 0) { flag = 0; break; } } if (flag) printf("%d\n", i); } return (0); }
素数算法2.0
上面是非常低效的,低效的原因在于我们遍历每个数字的时候都会对同样的数字进行求余操作.
什么意思呢?
举例来说:
当N = 10的时
我们要遍历的数字是:
3 4 5 6 7 8 9 10
遍历时,
每个数字都会都2进行整除运算.
3 % 2, 4 % 2, 5 % 2, 6 % 2, 7 % 2, 8 % 2, 9 % 2, 10 % 2.
反向思维一下,如果我们发现一个数字是2的倍数,那么这个数字就能够被2整除,那么他肯定就不是素数.
以此类推
每次重复的操作还有对3, 4, 5, 6 …. n-1;
因此可以肯定的是,如果一个数是莫个数的倍数,这个数就可以肯定不是素数了.
数学上有如下的算数定理:
一个自然数,要么本身是一个质数,要么可以写成一系列质数的乘积.
由此可知,是质数乘积的数肯定不是质数.
在此基础上,古希腊数学家埃拉托斯特尼所提出了一种快速检定素数的算法:(也就是我们上面描述的内容)
要得到自然数n以内的全部素数,必须把不大于根号n 的所有素数的倍数剔除,剩下的就是素数。
简单描述一下,求10以内的素数:
2 3 4 5 6 7 8 9 10
划掉2的倍数(2是素数) :剩下的还有
2 3 5 7 9
划掉3的倍数
2 3 5 7
划掉5的倍数,由于根号10 是小于5的,所以不用划5的倍数;
最终输出结果
2 3 5 7;
由此我们得到如下算法:
#include <string.h> #include <stdio.h> #define MAXSIZE 1000001 /* *采用Eratosthenes筛法, 给定范围n以内的素, *划掉根号n以内的素数的倍数的数, 剩下的都是素数. *prime[]数组保存求得的素数 *如果为0,则是素数, 反之则不是 *被划掉的数字我们用1表示. */ char prime[MAXSIZE]; void InitPrime() { int i, j; memset(prime, 0, sizeof(prime)); /* 划掉根号N范围内所有素数的倍数, 根号N通过 i * i 表示 */ for (i = 2; i * i < MAXSIZE; i++) if (!prime[i]) /* j不用从2*i开始,而是直接从i*i开始这里比较难理解,具体原因看下面解释*/ for (j = i * i; j < MAXSIZE; j += i) prime[j] = 1; } int main(void) { int i; InitPrime(); /* 打印200以内的素数 */ for (i = 2; i < 200; i++) { if (!prime[i]) printf("%d\n", i); } return(0); }
内存循环每次增加i的倍数,一般我们会从i的2倍开始;
但是通过看下面这张图片我们可以看出,其实j不用从2的倍数开始,因为2 * i已经在划掉2的倍数的时候被划掉了,同理3*i也已经在3的倍数里面被划掉了,
也就是说直到i * i ,之前所有比i小的倍数都已经被比 i小的素数的倍数 划掉了(表达得不是很清楚 -.- ,看了图片应该好懂一些 ).请看下图:
i表示素数,j表示素数的倍数的位置,红色方框表示我们内层循环 j ( i * i) 开始的位置.
从图中我们可以看出,
比i的小的倍数都在遍历之前被划掉了.
比如当i = 5的时候,
5* 2(遍历素数2的倍数的时候),
5 * 3(遍历素数3的倍数的时候),
5 * 4(这个不是素数,我们稍后讨论),
都已经被划掉了,所以不用再重复划掉了.
由此我们可以看出,j从i*i开始是合理的,而且随着i的增加,我们所需要遍历的比i小的倍数会逐渐增加,采用从i*i开始遍历,又进一步缩减了我们遍历的时间.
下面内容还在验证中,不要看 :-)
细心的同学可能已经看到,从i*i的倍数往下遍历的时候,有很位置我们也是已经划过了,
比如:
在5*6的位置,
又因为5 * 6 = 3 * 10 = 3 * 2 * 5 = 30的位置,也就是2 * 3 * 5;
就是说在我们遍历2*15时,30我们划掉过一次.
在遍历3 * 10的时候,30的位置又被我们划掉过一次.
那么从i*i开始以后的那个位置是肯定不会被前面的素数的倍数划掉的呢?
先从3开始看:
3 * 4, 4
7ff7
肯定会被划掉(因为2 * 2)
3 * 5不会被划掉.
3 * 6被划掉.
3 * 7不会.
3 * 8被划掉.
3 * 9被划掉.
也就是前面没有出现过的素数的倍数,肯定不会被划掉,因此在遍历的时候,我们在寻找素数倍数的时候,只需要寻找素数的素数倍数,但是这里会出现一个问题,因为我们本身就是在查找素数,那么我们怎么知道一个素数的倍数呢?
因为所有2的倍数肯定不是奇数,所以我们不用保存奇数字,我们假设我们数组里面存储的全部都是奇数,