逐步修改素数高效算法

来源:互联网 发布:世界军事软件 编辑:程序博客网 时间:2024/06/01 08:09

合数过滤筛选法

算法描述:我们知道,素数N不能被2~(N-1)间的任何数整除;反过来看,只要能被2~(N-1)间的任何数整除的N,都不是素数。所以我们可以采用一个简单的排除法:就是对N以内的所有数,只要逐个去除值为2~(N-1)的倍数的数,剩下的就是素数。

C语言实现

// 合数过滤筛选法 Ver1 
// 参数:n 求解n以内(包括n)的素数
// 返回值:n以内素数个数
int CompositeNumFilterV1(int n)
{
 int i, j;
 // 素数数量统计
 int count = 0;
 // 分配素数标记空间,结合后文思考为何+1
 char* flag = (char*)malloc( n+1 ); 
 
 // 初始化素数标记
 for (i=2; i<=n; i++)
 {
   // 为什么*(p+i)要写成flag[i]呢?可读性更佳尔
   flag[i] = 1;
 }
 
 // 写程序要注意排版和留空,方便阅读,也可减少出错几率
 // 以2~(N-1)为因子过滤合数
 for (i=2; i < n; i++)
 {
   for (j=2; i*j <= n; j++)
   {
    // i*j是由i,j两整数相乘而得,显然不是素数
    flag[i*j] = 0;
   }
 }
 
 // 统计素数个数
 for (i=2; i<=n; i++)
 {
   // 其实if(flag)就其同样作用了,但这么写是有留言的
   // 请参阅《C语言程序设计常见错误剖析及解决之道》一文
   if (1 == flag[i]) count++;
 }
  
 // 因输出费时,且和算法核心相关不大,故略
 
 // 释放内存,别忘了传说中的内存泄漏
 free(flag);
 
 return count;
}

在上文给出的main函数中以不同参数调用CompositeNumFilterV1函数,得到执行结果如下:

[100000]以内素数个数:9592, 计算用时:15毫秒
[1000000]以内素数个数:78498, 计算用时:125毫秒
[5000000]以内素数个数:348513, 计算用时:2578毫秒
[10000000]以内素数个数:664579, 计算用时:6281毫秒

注:因程序是非独占性运行的,所以时间不是完全精确的,但基本能反映实情

显然,比上文中的试除法要快,而且谁都可以看到上例是一个未经优化的粗陋版本,好多地方是三藏故意采用比较低效做法,为了与后文的优化版比较,凸显优化之重要,也为了初学者记住别采用类似低效做法,下面我们开始优化之旅

优化分析

上面CompositeNumFilterV1函数存在的问题有:

1.   在外层循环,需要一直执行到n-1吗?不要,因为n/2~n-1间的数显然不能整出n 

2.   在内层循环中重复使用i*j显然是低效的,考虑到计算机中加减运算速度比乘除快,可以考虑变乘法为加法

3.   在循环修改flag过程中,其实有很多数会被重复计算若干次,比如6=2*3=3*2,会被重复置0,类似操作很多,所以我们得设法避免或减少flag重复置0

据上述分析,我们可将程序优化如下:

// 合数过滤筛选法 Ver2 
// 参数:n 求解n以内(包括n)的素数
// 返回值:n以内素数个数
int CompositeNumFilterV2(int n)
{
 int i, j;
 // 素数数量统计
 int count = 0;
 // 分配素数标记空间,明白+1原因了吧,因为浪费了一个flag[0]
 char* flag = (char*)malloc( n+1 ); 
 
 // 初始化素数标记,要高效点咯
 flag[2] = 1;
 // 注意是i<n不是上例中的i<=n了,理由自思
 for (i=3; i<n; i++)
 {
   flag[i++] = 1;
   // 偶数自然不是素数,直接置0好了
   flag[i] = 0;
 }
 // n为奇数
 if (n%2 != 0)
 {
   flag[n] = 1;
 }
 
 // 从3开始filter,因为2的倍数早在初始化时代就干掉了
 // 到n/2止的理由还要说吗
 for (i=3; i <= n/2; i++)
 {
   // i是合数,请歇着吧,因为您的工作早有您的质因子代劳了
   if (0 == flag[i]) continue;
  
   // 从i的2倍开始过滤,变乘法为加法
   for (j=i+i; j <= n; j+=i)
   {
    flag[j] = 0;
   }
 }
 
 // 统计素数个数
 for (i=2; i<=n; i++)
 {
   if (flag[i]) count++;
 }
  
 // 因输出费时,且和算法核心相关不大,故略
 
 // 释放内存,别忘了传说中的内存泄漏
 free(flag);
 
 return count;
}

再来调用CompositeNumFilterV2得到执行结果:

[100000]以内素数个数:9592, 计算用时:n太小,时间精度不够
[1000000]以内素数个数:78498, 计算用时:31毫秒
[5000000]以内素数个数:348513, 计算用时:453毫秒
[10000000]以内素数个数:664579, 计算用时:1062毫秒
[100000000]以内素数个数:5761455, 计算用时:12973毫秒

哇哇,比昨天的试除发快了好多倍,可见算法的威力,值得好好学习,别说学算法没用咯。

上例着那个计算一亿以内的素数只要约13秒,应该算不错了,今天是否可以休息了呢?No,我们要追求极限!

intCompositeNumFilterV3(int n)
{
 int i, j;
 // 素数数量统计
 int count = 0;
 // 分配素数标记空间,明白+1原因了吧,因为浪费了一个flag[0]
 char* flag = (char*)malloc( n+1 );
 // 干嘛用的,请仔细研究下文
 int mpLen = 2*3*5*7*11*13;
 char magicPattern[mpLen];
 // 奇怪的代码,why,思考无法代劳,想!
 for (i=0; i<mpLen; i++)
 {
   magicPattern[i++] = 1;
   magicPattern[i++] = 0;
   magicPattern[i++] = 0;
   magicPattern[i++] = 0;
   magicPattern[i++] = 1;
   magicPattern[i] = 0;
 }
 for (i=4; i<=mpLen; i+=5)
   magicPattern[i] = 0;
 for (i=6; i<=mpLen; i+=7)
   magicPattern[i] = 0;
 for (i=10; i<=mpLen; i+=11)
   magicPattern[i] = 0;
 for (i=12; i<=mpLen; i+=13)
   magicPattern[i] = 0;
 
 // 新的初始化方法,将2,3,5,7,11,13的倍数全干掉
 // 而且采用memcpy以mpLen长的magicPattern来批量处理
 int remainder = n%mpLen;
 char* p = flag+1;
 char* pstop = p+n-remainder;
 while (p < pstop)
 {
   memcpy(p, magicPattern, mpLen);
   p += mpLen;
 }
 if (remainder > 0)
 {
   memcpy(p, magicPattern, remainder);
 }
 flag[2] = 1;
 flag[3] = 1;
 flag[5] = 1;
 flag[7] = 1;
 flag[11] = 1;
 flag[13] = 1;
 
 // 从17开始filter,因为2,3,5,7,11,13的倍数早被kill了
 // 到n/13止的,哈哈,少了好多吧
 int stop = n/13;
 for (i=17; i <= stop; i++)
 {
   // i是合数,请歇着吧,因为您的工作早有您的质因子代劳了
   if (0 == flag[i]) continue;
  
   // 从i的17倍开始过滤
   int step = i*2;
   for (j=i*17; j <= n; j+=step)
   {
    flag[j] = 0;
   }
 }
 
 // 统计素数个数
 for (i=2; i<=n; i++)
 {
   if (flag[i]) count++;
 }
  
 // 因输出费时,且和算法核心相关不大,故略
 
 // 释放内存,别忘了传说中的内存泄漏
 free(flag);
 
 return count;
}

再看CompositeNumFilterV3执行结果:

[1000000]以内素数个数:78498, 计算用时:15毫秒
[5000000]以内素数个数:348513, 计算用时:203毫秒
[10000000]以内素数个数:664579, 计算用时:515毫秒
[100000000]以内素数个数:5761455, 计算用时:6421毫秒

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 楼下9米垃圾房怎么办 在工厂上班得了职业病怎么办 自来水被农药水污染了怎么办 雾霾天头疼恶心怎么办? 夫妻住宾馆一个没有身份证怎么办 医保报销后认定工伤怎么办 结肠癌术后复查有息肉怎么办 无蒂息肉恶变要怎么办 贤者时间很长怎么办 鸡吃了酒米醉了怎么办 自填脂肪乳房脂肪液化怎么办 中国人在外国遇到危险怎么办 dnf刷图卡住了怎么办 dbf深渊怪卡住了怎么办 dnf86级没任务了怎么办 dnf二觉任务没了怎么办 脚趾甲变空向上翘怎么办 汽油车加了一点柴油怎么办 柴油车辆环保检测功率不足怎么办 加95加错一次92怎么办 新车95加错92油怎么办 加不到95号汽油怎么办 去新疆没95号油怎么办 黄龙300加了92怎么办 gla错加92号油 怎么办 95和98混加了怎么办 沥青车可以停在居民区怎么办 汽油进到眼睛了怎么办 汽油进了眼睛里怎么办 眼睛里面进了汽油怎么办 脱硫塔里的二氧化硫高怎么办 恐怖黎明铁匠选错怎么办 堡垒之夜草变色怎么办 火柴没有擦的了怎么办 乙醚倒进下水道了怎么办 乙醚和水不分层怎么办 乙醚闻多了头晕怎么办 爱乐维吃了便秘怎么办 刮完滑石粉墙面很软怎么办 被硫酸泼到皮肤怎么办 头磕了一下头晕怎么办