本文以“判断101-200之间有多少个素数”介绍求素数的7种解法:
- 遍历每个值进行相除。
- 取开方根,只遍历2~开方根。
- 间隔6个数只取2个值计算,结合开方根。
- 从头开始,保存每个素数,遍历时只需要判断是否能让集合中的素数整除。
- 保存素数,结合开方根优化。
- 简单线性筛法。
- 优化版线性筛法。
该题较简单,但是没想到仔细想的时候发现解法这么多。
首先需要了解什么是素数:只能被1和本身整除的整数,就是素数。
最基础解法:遍历每个值相除
这是刚接触到题目时,每个人第一个想到的解法:用2~(该数-1)来除,如果都不能整除,这个数就是素数。
1 | /// <summary> |
但是,这里存在大量多余的判断。
遍历2~开方根
思想:一个数=前面某个数 X n ,推断出来,肯定存在“某个数” < n,或者 n< “某个数”。这样最极端的情况,就是n=这个“某个数”,得到数 = n2;所以我们只需要除前面这n个数,后面的操作都是重复的,就可以不除了。
1 | /// <summary> |
间隔6个数只取2个值计算
该解法是看到这个博客判断质数/素数——我知道的最快的方法 才想到的。通过观察,2、3包揽了几乎绝大部分的合数。
所有数可以大致以如下方式表示:
… | 6x, | 6x+1, | 6x+2, | 6x+3, | 6x+4, | 6x+5, | 6x+6 | ==> |
---|---|---|---|---|---|---|---|---|
==> | 6(x+1), | 6(x+1)+1, | 6(x+1)+2, | 6(x+1)+3, | 6(x+1)+4, | 6(x+1)+5, | 6(x+1)+6 | … |
其中6x, 6x+2, 6x+3, 6x+4都是合数,剩下 6x+1, 6x+5才存在素数的可能性。故实际,每6个数,只需要检查两个数!
1 | /// <summary> |
用前面存在的素数来遍历判断
合数能被素数整除,但是素数不能被其他素数整除。所以只需要保存前面获取的素数,后面的数直接除前面这些素数,不能整除,就也是素数!
1 | /// <summary> |
结合开方根用前面存在的素数来遍历判断
上面的方法同样存在重复判断,实际上当除以的素数大于开方根的时候,后面的素数就更加不可能了,该数直接可以断定是素数了。he = su1 x su2,当su1< sqrt(he)时,su2 > sqrt(he);当su1>sqrt(he)时,su< sqrt(he),这里就重复了。所以当前面一段找不到结果,后面的肯定也找不到。
1 | /// <summary> |
简单线性筛法
从头开始,剩下的最小的数肯定是素数,然后根据这个数,翻倍剔除掉剩下集合中的合数;最后转移该素数,继续轮回执行。执行到整个集合为空为止,全部素数已经转移出来。
1 | /// <summary> |
这里可以用递归,但是数字大的时候会内存溢出。所以我取消了递归的方式。同时还得注意int溢出的问题,会出现46957*91467==48623为true的现象。因为int有最大值,超过就会循环取值了,正转负,负转正。
优化版线性筛法
上面的解法同样存在重复操作。理想情况,一个数只用剔除一次。
该解法是看到这个文章线性筛法求素数的原理与实现 才想到的。
这里讲解一下:
合数 = A x B,当A/B又是合数时,重复下去,合数 = …x…x素数 ==> 最大素数 x 第二大素数 x … x 最小素数
所以只要我们找到最小素数,把它当做B,而A又唯一(递增的i),该合数就唯一确定了。
1 |
|
结尾得提一下,上面的算法,三个优化版本,在数据量不大的情况下(十万左右),线性筛选法优势不大,间隔6的那种最快;但是当达到百万以上,线性是最快的!
每次运行的时间都会有一点点的区别。注意是毫秒,千分之一秒。所以上面的数据出来是超级快的。
git代码库: Codes