求质数算法的N种境界 (N 10) zz

★引子

  前天,俺在《俺的招聘经验[4]:通过笔试答题能看出啥?》一文,以"求质数"作为例子,介绍了一些考察应聘者的经验。由于本文没有政治敏感内容,顺便就转贴到俺在CSDN的镜像博客。
  昨天,某个CSDN网友在留言中写道:

老实说,这个程序并不好写,除非你背过这段代码
如果只在纸上让别人写程序,很多人都会出错
但是如果给一台电脑,大多数人都会把这个程序调试正确
出这个题目没啥意义
只能让别人觉得你出题水平低


  首先,这位网友看帖可能不太仔细。俺在文中已经专门强调过了,评判笔试答题,"思路和想法"远远比"对错"更重要,而他/她依然纠结于对错;其次,这位网友居然觉得这道题目没啥意义,这让俺情何以堪啊?!看来,有相当一部分网友完全没有领略到此中之奥妙啊!
  算了,俺今天就豁出去了,给大伙儿抖一抖这道题目的包袱。当然,抖包袱的后果就是:从今天开始,就得把"求质数"这道题从俺公司的笔试题中去掉,然后换上另外一道全然不同的。这么好的一道题要拿掉,真是于心不忍啊 :-(

★题目

  好,言归正传。下面俺就由浅入深,从各种角度来剖析这道题目的奥妙。
  为了避免被人指责为"玩文字游戏"(有些同学自己审题不细,却抱怨出题的人玩文字游戏),在介绍各种境界之前,再明确一下题意。
  前一个帖子已经介绍过,求质数可以有如下2种玩法。

◇需求1

请实现一个函数,对于给定的整型参数 N,该函数能够把自然数中,小于 N 的质数,从小到大打印出来。
比如,当 N = 10,则打印出
2 3 5 7

◇需求2

请实现一个函数,对于给定的整型参数 N,该函数能够从小到大,依次打印出自然数中最小的 N 个质数。
比如,当 N = 10,则打印出
2 3 5 7 11 13 17 19 23 29

★试除法

  首先要介绍的,当然非"试除法"莫属啦。考虑到有些读者是菜鸟,稍微解释一下。
  "试除",顾名思义,就是不断地尝试能否整除。比如要判断自然数 x 是否质数,就不断尝试小于 x 且大于1的自然数,只要有一个能整除,则 x 是合数;否则,x 是质数。
  显然,试除法是最容易想到的思路。不客气地说,也是最平庸的思路。不过捏,这个最平庸的思路,居然也有好多种境界。大伙儿请看:

◇境界1

  在试除法中,最最土的做法,就是:
  假设要判断 x 是否为质数,就从 2 一直尝试到 x-1。这种做法,其效率应该是最差的。如果这道题目有10分,按照这种方式做出的代码,即便正确无误,俺也只给1分。

◇境界2

  稍微聪明一点点的程序猿,会想:x 如果有(除了自身以外的)质因数,那肯定会小于等于 x/2,所以捏,他们就从 2 一直尝试到 x/2 即可。
  这一下子就少了一半的工作量哦,但依然是很笨的办法。打分的话,即便代码正确也只有2分

◇境界3

  再稍微聪明一点的程序猿,会想了:除了2以外,所有可能的质因数都是奇数。所以,他们就先尝试 2,然后再尝试从 3 开始一直到 x/2 的所有奇数。
  这一下子,工作量又少了一半哦。但是,俺不得不说,依然很土。就算代码完全正确也只能得3分。

◇境界4

  比前3种程序猿更聪明的,就会发现:其实只要从 2 一直尝试到√x,就可以了。估计有些网友想不通了,为什么只要到√x 即可?
  简单解释一下:因数都是成对出现的。比如,100的因数有:1和100,2和50,4和25,5和20,10和10。看出来没有?成对的因数,其中一 个必然小于等于100的开平方,另一个大于等于100的开平方。至于严密的数学证明,用小学数学知识就可以搞定,俺就不啰嗦了。

◇境界5

  那么,如果先尝试2,然后再针对 3 到√x 的所有奇数进行试除,是不是就足够优了捏?答案显然是否定的嘛?写到这里,才刚开始热身哦。
  一些更加聪明的程序猿,会发现一个问题:尝试从 3 到√x 的所有奇数,还是有些浪费。比如要判断101是否质数,101的根号取整后是10,那么,按照境界4,需要尝试的奇数分别是:3,5,7,9。但是你发现 没有,对9的尝试是多余的。不能被3整除,必然不能被9整除......顺着这个思路走下去,这些程序猿就会发现:其实,只要尝试小于√x质数即可。而这些质数,恰好前面已经算出来了(是不是觉得很妙?)。
  所以,处于这种境界的程序猿,会把已经算出的质数,先保存起来,然后用于后续的试除,效率就大大提高了。
  顺便说一下,这就是算法理论中经常提到的:以空间换时间

◇补充说明

  开头的4种境界,基本上是依次递进的。不过,境界5跟境界4,是平级的。在俺考察过的应聘者中,有人想到了境界4但没有想到境界5;反之,也有人想到境界5但没想到境界4。通常,这两种境界只要能想到其中之一,俺会给5-7分;如果两种都想到了,俺会给8-10分。
  对于俺要招的"初级软件工程师"的岗位,能同时想到境界4和境界5,应该就可以了。如果你对自己要求不高,仅仅满足于浅尝辄止。那么,看到这儿,你就可以打住了,无需再看后续的内容;反之,如果你比较好奇或者希望再多学点东西,请接着往下看。

★筛法

  说完"试除法",再来说说筛法(维基百科的解释在"这里")。俺不妨揣测一下:本文的读者,应该有2/3以上,从来没有听说过筛法。所以捏,顺便跟大伙儿扯扯蛋,聊一下筛法的渊源。
  这个筛法啊,真的是一个既巧妙又快速的求质数方法。其发明人是公元前250年左右的一位希腊大牛——埃拉托斯特尼。为啥说他是大牛捏?因为他本人精通多个学科和领域,至少包括:数学、天文学、地理学(地理学这个词汇,就是他创立的)、历史学、文学(他是一个诗人)。真的堪称"跨领域的大牛"。
  他最让俺佩服的是:仅仅用简单的几何方法,测量出了地球的周长、地球与月亮的距离、地球与太阳的距离、赤道与黄道的夹角......而且,这些计算结 果跟当代科学家测出的,相差无几。要知道他生活的年代,大概相当于中国的春秋战国。而咱们的老祖宗,一直到明朝还顽固地坚信:天是圆的、地是方的、月亮会 被天狗给吃喽......
  好了,扯蛋完毕,言归正传。
  估计很多人把筛法仅仅看成是一种具体的方法。其实,筛法还是一种很普适的思想。在处理很多复杂问题的时候,都可以看到筛法的影子。那么,筛法如何求质数捏,说起来很简单:
  首先,2是公认最小的质数,所以,先把所有2的倍数去掉;然后剩下的那些大于2的数里面,最小的是3,所以3也是质数;然后把所有3的倍数都去掉,剩下的那些大于3的数里面,最小的是5,所以5也是质数......
  上述过程不断重复,就可以把某个范围内的合数全都除去(就像被筛子筛掉一样),剩下的就是质数了。维基百科上有一张很形象的动画,能直观地体现出筛法的工作过程。

不见图、请FQ



  明白了"筛法"的原理,大伙儿应该看出,筛法在速度上是明显优于"试除法"的。当然,筛法的程序实现也分为不同的境界。而且,筛法可讲究的门道更多了。下面,俺分别从不同角度,聊一聊筛法都有哪些讲究。

◇如何确定质数的分布范围?

  这是采用筛法首先会碰到的问题。文本开头给出的那两种需求,其处理的方式完全不同,俺分别说一下。

需求1
  对于需求1,这个自然不是问题。因为在需求1中,质数的分布范围就是 N,已经给出了,很好办。

需求2
  但是对于需求2,就难办了。因为需求2给出的 N,表示需要打印的质数的个数,那么这 N 个质数会分布在多大的范围捏?这可是个头疼的问题啊。
  但是,来应聘的程序猿如果足够牛的话,当然不会被这个问题难倒。因为素数的分布,是有规律可循滴——这就是大名鼎鼎的素数定理。
  稍微懂点数学的,应该知道素数的分布是越往后越稀疏。或者说,素数的密度是越来越低。而素数定理,说白了就是数学家找到了一些公式,用来估计某个范围内的素数,大概有几个。在这些公式中,最简洁的就是x/ln(x),公式中的 ln 表示自然对数(估计很多同学已经忘了啥叫自然对数)。假设要估计1,000,000以内有多少质数,用该公式算出是72,382个,而实际有78,498个,误差约8个百分点。该公式的特点是:估算的范围越大,偏差率越小。
  有了素数定理,就可以根据要打印的质数个数,反推出这些质数分布在多大的范围内。因为这个质数分布公式有一定的误差(通常小于15%)。为了保险起见,把反推出的素数分布范围再稍微扩大15%,应该就足够了。

  可能有同学会质疑俺:谁有这么好的记性,能够在笔试过程中背出这些质数分布公式捏?
  俺觉得:背不出来是正常滴。但是,对于有一定数学功底的应聘者,假如他/她知道质数分布公式,即便不能完整写出来,只要在答题中体现出:"此处通过质数分布公式推算范围",那么俺也是认可滴。
  再啰嗦一次:关键是看idea!

◇如何设计存储容器?

  知道了分布范围,接下来就得构造一个容器,来存储该范围内的所有自然数;然后在筛的过程中,把合数筛掉。那么,这个容器该如何设计捏?不同层次的程序猿,自然设计出来的容器也不同啦。

境界1
  照例先说说最土的搞法——直接构造一个整型的容器。在筛的过程中把发现的合数删除掉,最后容器中就只剩下质数了。
  为啥说这种搞法最土捏?
  首先,整型的容器,浪费内存空间。比方说,你用的是32位的C/C++或者是Java,那么每个 int 都至少用掉4个字节的内存。当 N 很大时,内存开销就成问题了。
  其次,当 N 很大时,频繁地对一个大的容器进行删除操作可能会导致频繁的内存分配和释放(具体取决于容器的实现方式);而频繁的内存分配/释放,会导致明显的CPU占用并可能造成内存碎片。

境界2
  为了避免境界1导致的弊端,更聪明的程序猿会构造一个定长的布尔型容器(通常用数组)。比方说,质数的分布范围是1,000,000,那么就构造一个 包含1,000,000个布尔值的数组。然后把所有元素都初始化为 true。在筛的过程中,一旦发现某个自然数是合数,就以该自然数为下标,把对应的布尔值改为 false。
  全部筛完之后,遍历数组,找到那些值为 true 的元素,把他们的下标打印出来即可。
  此种境界的好处在于:其一,由于容器是定长的,运算过程中避免了频繁的内存分配/释放;其二,在某些语言中,布尔型占用的空间比整型要小。比如C++的 bool 仅用1字节
注:C++标准(ISO/IEC 14882)没有硬性规定 sizeof(bool)==1,但大多数编译器都实现为一字节。

境界3
  虽然境界2解决了境界1的弊端,但还是有很大的优化空间。有些程序猿会想出按位(bit)存储的思路。这其实是在境界2的基础上,优化了空间性能。俺觉得:C/C++出身的或者是玩过汇编语言的,比较容易往这方面想。
  以C++为例。一个bool占用1字节内存。而1个字节有8个比特,每个比特可以表示0或1。所以,当你使用按位存储的方式,一个字节可以拿来当8个 布尔型使用。所以,达到此境界的程序猿,会构造一个定长的byte数组,数组的每个byte存储8个布尔值。空间性能相比境界2,提高8倍(对于C++而 言)。如果某种语言使用4字节表示布尔型,那么境界3比境界2,空间利用率提高32倍。

★总结

  看到俺写"总结"二字,很多网友心想:总算看完了,知道该怎么求质数才是最优的了。
  其实,你们又错了,本文才写了不到一半。考虑到篇幅已经有点长,而且俺打了这么多字,也有点累了,暂时刹住话匣子,下次接着聊。
  希望看了今天这个介绍,大伙儿应该明白一个道理:山外有山、天外有天。每一个技术领域里面的每一个细小的分支,深究下去都有很多的门道与奥妙。在你深究的过程中,必然会学到很多东西。深究的过程也就是你能力提高的过程。
  本文后续的内容,会介绍刚才提到的按位存储法还有哪些缺陷,该如何解决。另外,还会介绍其它一些求质数的方法。


版权声明
本博客所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者编程随想和本文原始地址:
http://program-think.blogspot.com/2011/12/prime-algorithm-1.html

转载于:https://www.cnblogs.com/end/archive/2013/05/08/3067117.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/378429.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python匿名函数---排序

一、列表的排序 nums [1,2,3,5,4,7,87,4,9,56,44,7,5] nums.sort()#默认从小到大排序 nums#结果为:[1, 2, 3, 4, 4, 5, 5, 7, 7, 9, 44, 56, 87]nums [1,2,3,5,4,7,87,4,9,56,44,7,5] nums.sort(reverseTrue)#从大到小排序 nums#结果为:[87, 56, 44, …

linux下怎么查kill某个进程,Linux下查询进程PS或者杀死进程kill的小技巧

假设我们要kill掉tomcat:那么我们首先需要tomcat的进程号pid:ps -aux | grep tomcat记下tomcat的PID后,执行:kill PID(tomcat)好了,就到这里....路人甲:小的们,灭了这个欺骗人民情感的家伙&…

opencv模板匹配

matchTemplate函数参数 模板匹配是通过模板在采集到的原图像进行滑动寻找与模板图像相似的目标。模板匹配不是基于直方图的方式,而是基于图像的灰度匹配。 6种匹配度量方法: 平方差匹配法CV_TM_SQDIFF 归一化平方差匹配法CV_TM_SQDIFF_NORMED 相关匹配…

Java程序设计4——集合类

1 JAVA集合概述 Java集合封装了一系列数据结构比如链表、二叉树、栈、队列等,然后提供了针对这些数据结构的一系列算法比如查找、排序、替换,使编程难度大大降低。(这句话有可能是非法…

python与tensorflow知识点截图集锦(持续囤积)

目录前言conda环境管理python语法【1】语言属性【2】代码缩进问题【3】input和output函数与print函数【4】关键字与简单数据类型与简单运算符【5】利用缩进体现逻辑关系【6】数据结构:列表与元组【7】数据结构:字典【8】数据结构:集合【8】基…

linux测试固态硬盘读写速度,在 Linux 上检测 IDE/SATA SSD 硬盘的传输速度

你知道你的硬盘在 Linux 下传输有多快吗?不打开电脑的机箱或者机柜,你知道它运行在 SATA I (150 MB/s) 、 SATA II (300 MB/s) 还是 SATA III (6.0Gb/s) 呢?你能够使用 hdparm 和 dd 命令来检测你的硬盘速度。它为各种硬盘的 ioctls 提供了命…

Opencv——批量处理同一文件夹下的图片(解决savedfilename = dest + filenames[i].substr(len)问题)

文章目录前言一、完整代码二、实现效果前言 第一份代码实现了批量修改同一文件夹下图片的尺寸,有其他需求时仅需修改处理部分的代码以及文件夹路径。 第二份代码实现了批量截取同一文件夹下每张图片的ROI区域作为结果保存,注意截取后按下enter键才会跳到…

处理文件、摄像头和图形用户界面

1、基本I/O脚本 1.1 读/写图像文件 import numpy import cv2#利用二维Numpy数组简单创建一个黑色的正方形图像 img numpy.zeros((3,3),dtypenumpy.uint8) img #结果为:array([[0, 0, 0],[0, 0, 0],[0, 0, 0]], dtypeuint8)img.shape#结果为:(3, 3)###…

linux桌面天气,Ubuntu 14.10中安装和配置天气应用

对于操作系统平台而言,有各种小插件功能方便用户日常应用。在Ubuntu桌面中提供天气信息的功能,可以使用Unity Dash和桌面应用来获得相关信息,比如Typhoon。但对于用户而言,可以提供快速查询天气状况和温度数据,并且只需…

linux批处理操作系统_批处理操作系统

linux批处理操作系统批处理操作系统 (Batch Processing Operating System) When we are working in an environment there is a restriction of making use of computer resources effectively and improvement in the programmers output. When we are working with tapes a l…

STL容器及其简单应用(stack、priority_queue、vector、deuqe、list、map/multimap、set/multiset)

目录前言【1】stack操作以及应用stack的几个核心接口利用stack完成进制转换【2】priority_queue操作以及应用priority_queue的几个核心接口利用priority_queue完成合并果子问题【3】vector操作以及应用vector的几个核心接口利用vector完成随机排序【4】deuqe(双向队列)操作以及…

已知一个掺杂了多个数字字符的中文名拼音,去掉所有数字字符之后,形式为“名”+空格+“姓”;并且名和姓的首字母大写,其他小写,要求输出姓名全拼,且全为小写。(后附详细样例说明)

已知一个掺杂了多个数字字符的中文名拼音,去掉所有数字字符之后,形式为“名”空格“姓”;并且名和姓的首字母大写,其他小写,要求输出姓名全拼,且全为小写。(后附详细样例说明) 【输入…

在一个风景秀丽的小镇,一天早上,有N名晨跑爱好者(编号1~N)沿着优雅的江边景观道朝同一方向进行晨跑

【问题描述】 在一个风景秀丽的小镇,一天早上,有N名晨跑爱好者(编号1~N)沿着优雅的江边景观道朝同一方向进行晨跑,第i名跑者从位置si处起跑,且其速度为Vi。换句话说,对所有的实数t≥0,在时刻t时第i名跑者的…

linux内核测试,Linux内核测试的生命周期

内核持续集成(CKI)项目旨在防止错误进入 Linux 内核。在 Linux 内核的持续集成测试 一文中,我介绍了 内核持续集成Continuous Kernel Integration(CKI)项目及其使命:改变内核开发人员和维护人员的工作方式。本文深入探讨了该项目的某些技术方面&#xff…

【视觉项目】【day3】8.22号实验记录(利用canny检测之后的来进行模板匹配)

【day3】8.22号实验记录(几乎没干正事的一天,利用canny检测之后的来进行模板匹配) 今天没搞代码,主要是问研究生学长工业摄像头的接法的,学长也不知道,明天问问老师。。。 晚上搞了一下canny之后的模板匹配…

linux dd 大文件下载,Linux dd+grep 大文件二分查找

Linux dd 命令用于读取、转换并输出数据。dd 可从标准输入或文件中读取数据,根据指定的格式来转换数据,再输出到文件、设备或标准输出。参数说明(dd --help)Usage: dd [OPERAND]...or: dd OPTIONCopy a file, converting and formatting according to th…

如何让没有安装网页中所需字体的用户也能得到一致的浏览效果【转】

今天给大家谈一个关于字体的话题,我们在做项目的过程中会遇到一些在psd中的字体在自己的电脑中没有安装,或者是一些特殊的文字,通常的做法是把它切成图片,但是如果这个站是多个语言的,那我们是不是把每个语言的都切一张图片呢&…

【视觉项目】【day4】8.24号实验记录(消除瓶子内部“边缘”)

思路分析以及代码 思路1:使用findContours函数,设置轮廓为最外部RETR_EXTERNAL,结果发现结果仍然是所有轮廓。 思路2:先二值化,然后进行闭操作,然后canny,得到的轮廓确实比之前少很多&#xff…

国产操作系统和linux 之间的关系,为何国产系统大多基于开源Linux?操作系统从0做起到底有多难?...

今年貌似是国产操作系统的“爆发”之年,除了老牌的银河麒麟、中标麒麟、深度之外,中兴近日发布了自己的“新支点”,华为也公开了自研的操作系统“鸿蒙”。纵观这些国产操作系统,大多基于开源的Linux。那么为什么我们不可以从0开始…

js 第四课

正则表达式:RegExp对象 正则表达式描述一个字符模式的对象,或者说用某种模式去匹配一类字符串的一个公式。 1.创建 可以用RegExp构造函数和直接量两种方式。正则表达式直接量被包含在一对"/"中. 1 var partern1 RegExp(\\d*); 2 …