引子
谈起求素数的问题,可能许多人嗤之以鼻,认为是编程学习最简单最基础的一部分,可是,真的是这样吗?
试问:当一个数足够大,或者对该算法的时间有严格的限制时,还是如此简单吗?
正文
乍眼一看,第一反应应该是这样的:
#include<stdio.h>
#include<Windows.h>
#include<time.h>
int IsPrime(int num)
{
int i;
if (num <= 1){
return 0;
}
if (num == 2){
return 1;
}
for (i = 2; i < num; i++){
if (num%i == 0){
return 0;
}
}
return 1;
}
int main()
{
int i,top = 100000;
int start = clock();
for (i = 2; i <= top; i++){
if (IsPrime(i)){
printf("%d ", i);
}
}
int end = clock();
printf("\n运行时间:%d\n", end-start);
system("pause");
return 0;
}
一个数除以比本身一半还要大的数,怎么可能除净!在判断的时候,每次都会判断n-2次,大大增加了程序运行的时间,所以,这种做法肯定是不可取的。此时,程序用时2520ms
一个数除以自身一半以上的数时,是不会除尽的,所以,可以在原程序上做简单修改,将IsPrime函数中的素数判断条件由num修改为num/2后,程序用时1709ms,完成算法的初步简化
可以继续在原程序上修改,引入#include<math.h>头文件后,将素数判断条件由上一步的n/2修改为sqrt(num),此时,程序用时842ms,完成算法的进一步简化。(由于因数是成对出现的,一个数的因数小于等于该数的开平方时,这个数的另一个因数势必会大于等于该数的开平方,例如:100,它的因数有1和100,2和50,4和25,5和20,10和10,可知,100的一个因数小于等于10时,与该因数成对的另一个因数必定大于等于10..........)
由此,可以通过程序运行的时间反馈看出,经过两次简化后,运行时间缩短了几近1/3
当然,由于素数除了2之外其他全是奇数,所以,也可以每次加2,减少判断数的个数;同时,判断时首先排除所有的偶数,然后在剩下的数中枚举进行判断
所以,经过仔细的深究后是这样的:
#include<stdio.h>
#include<Windows.h>
#include<time.h>
#include<math.h>
int IsPrime(int num)
{
int i;
if (num % 2 == 0){
return 0;
}
for (i = 3; i < sqrt(num); i+=2){
if (num%i == 0){
return 0;
}
}
return 1;
}
int main()
{
int i,top = 100000;
int start = clock();
printf("2 ");
for (i = 3; i <= top; i+=2){
if (IsPrime(i)){
printf("%d ", i);
}
}
int end = clock();
printf("\n运行时间:%d\n", end-start);
system("pause");
return 0;
}
这样的程序,对于普通的编程来说已经够用了,但是,对于有严格要求的算法似乎还是不太够用。
说完了试除法,下面来简单谈谈筛选法
随着对于算法及编程学习的不断深入,我们会接触到 埃拉托斯特尼筛法 -----由希腊著名的数学家提出的
其算式简述为:
要得到自然数n以内的全部素数,必须把不大于的所有素数的倍数剔除,剩下的就是素数
给出要筛数值的范围n,找出以内的素数。先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个素数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个素数5筛,把5留下,把5的倍数剔除掉;不断重复下去......
例如:列出2以后的所有数为
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ......
先用2去筛,筛去除2以外的2的所有倍数
2 3 5 7 9 11 13 15 17 19 21 23 25 ......
再用3去筛,筛去除3以外的3的所有倍数
2 3 5 7 11 13 17 19 23 25 ......
再用5去筛,筛去除5以外的5的所有倍数
2 3 5 7 11 13 17 19 23 ......
...... ......
直到筛选的数为空为止
#include<stdio.h>
#include<Windows.h>
#include<time.h>
#define N 100000
int flag[100000];
void IsPrime()
{
int i, j;
for (i = 2; i <= N; i++){ //InitArray
flag[i] = 1;
}
for (i = 2; i <= N; i++){
if (flag[i]){
printf("%d ", i);
}
for (j = 2 * i; j <= N; j += i){
flag[j] = 0;
}
}
}
int main()
{
int start = clock();
IsPrime();
int end = clock();
printf("\n程序运行时间:%d",end-start);
system("pause");
return 0;
}
此算法用一个数组存储所有可能为素数的数的状态(数组下标即表示该数),先初始化,再逐个最小素数的筛选其倍数,将状态置为0,再输出
当然,该算法还存在许多可以优化的地方,比如对同一数的状态会进行多次访问、空间占用比试除法占用的多............
完