当前位置:文档之家› 求素数列表和判断素数的算法

求素数列表和判断素数的算法

求素数列表和判断素数的算法有兴趣阅读本文的读者,应该对素数概念是十分熟悉的了。

用计算机程序实现素数计算,集中在2个主要问题上:∙判断一个正整数是否是素数-(算法A)∙求一定范围内的素数列表- (算法B)关于素数的算法,根据素数的数学性质,大家都会想到如下几个方面:∙用遍历求模的方式判断素数∙素数都是奇数,可以在奇数数列中寻找素数∙利用开方来缩小搜索的范围然后,求素数的计算是复杂的,如果算法写得不好,则耗时较高。

在百度百科“素数”条目中的算法程序,是值得商榷的。

很多方法是O(N2)的算法。

为此,在本文中探讨了求素数列表和判断素数这两个算法,力图使算法可以达到O(N Log(N))优化级别。

在本文中,算法语言选用C#。

1,判断素数的简单实现(算法A-1)///<summary>///算法A-1,判断素数///</summary>///<param name="number">待测正整数</param>///<returns>是否为素数(为了简化,1以下的整数皆为素数)</returns>public static bool IsPrime(int number){// 为了简化,1以下的整数皆为素数if(number <= 2){return true;}// 奇偶性if (number % 2 == 0){return false;}// 利用开方缩小范围,优化效果十分明显int range = (int)Math.Sqrt(number) + 1;// 从3开始的奇数列for (int current = 3; current <= range; current += 2){// 判断是否为素数if (number % current == 0){// 合数return false;}}return true;}A-1算法中,首先判断奇偶性,再判断从3开始判断待查数(number)是否是合数,若到Math.Sqrt(number)还没有查到是合数,即为素数。

这个方法是O(N1/2)级别的算法,所以很快,即使是最悲观的int.MaxValue,在本人的本机计算也可达到125ms的运行时间。

2,判断素数的优化算法(A-2)虽然这种算法速度比较快,但如果频繁使用A-1算法,累计起来开销就比较大了,假设有N次访问的话,算法级别就到了O(N3/2)。

因此,本人的解决方案是先计算出一定范围内的素数列表存放在哈希表中,多次调用就可以忽略查询时间了。

///<summary>///素数的Hash表///</summary>private static HashSet<int> Primes { get; set; }///<summary>///算法A-2,判断素数///</summary>///<param name="number">待测正整数</param>///<returns>是否为素数(为了简化,1以下的整数皆为素数)</returns>public static bool IsPrime2(int number){// 为了简化,1以下的整数皆为素数if (number <= 2){return true;}return Primes.Contains(number);}这种算法的前提是要求一个素数表,这就是算法B要研究的问题了。

3 求素数列表的简单算法(B-1)///<summary>///求素数列表的简单算法(算法B-1)///</summary>///<param name="range"></param>///<returns></returns>public static int[] GetPrimes(int range){List<int> primeList = new List<int>();// 从3开始的奇数列for(int current = 3; current <= range; current += 2){// 判断是否为素数bool isPrime = true;foreach (int prime in primeList){if (current % prime == 0){// 若为合数,进入下一数的判断isPrime = false;break;}}if (isPrime){// 加入素数列表primeList.Add(current);}}// 把2补入primeList.Insert(0, 2);return primeList.ToArray();}上述算法,是对小于N的数,遍历素数表,判断是否为合数,全不成立的即为素数,并加入素数表。

这个算法的一个特点是只对奇数数列进行遍历,从而使遍历次数减少了一半。

4,步长优化的算法(算法B-2)有读者会想到阴阳素数数列的问题,也就是除了2,3以外,素数要么在(6N-1)数列,要么在(6N+1)数列,这样,是不是就可以通过改变步长方式就可以提高速度?是的,本人也做过这样的实验,代码如下:///<summary>///求素数列表的简单算法(算法B-2)///</summary>///<param name="range"></param>///<returns></returns>public static int[] GetPrimes2(int range){List<int> primeList = new List<int>();int index = 1; // 步长下标int[] loops = new int[] { 2, 4 };//步长for (int current = 5; current <= maxValue; current += loops[index]) {// 判断是否为素数bool isPrime = true;foreach (int prime in primeList){if (current % prime == 0){// 若为合数,进入下一数的判断isPrime = false;break;}}if (isPrime){// 加入素数列表primeList.Add(current);}index ^= 1; //阴阳数列转换}// 把2,3补入primeList.Insert(0, 2);primeList.Insert(0, 3);return primeList.ToArray();}采用步长数组和异或操作切换,可以最大限度的不增加代码优化后的开销。

这样的优化取得了一定的优化效果,但效果不明显。

我分析,B-1是最多达到了50%的优化,而B-2是达到了33.3%的优化效果,所以优化不明显。

所以后面的算法,就忽略了B-2的这样的优化。

5,采用开方法优化的算法(算法-B-3)本文的重点来了求素数列表的一大挑战,是当求解的范围很大时,速度就会变得其慢。

在对int.MaxValue/ 10000规模计算中,大致是1910ms左右,在往上速度就很慢了。

分析原因,算法中的最大的瓶颈在于求模操作。

由于求模操作命中率不高,所以执行次数很多。

根据数论知识,可以得到下来推论:把全集X = {n|n <= N}, 可以分为两个子集,X1={n | n <= N1/2 + 1}和X2={n | N1/2 + 1 < n <=N}, Y1是X1中的素数子集, Y2是X2中的素数子集,则X2中的所有合数的因数,全属于Y1。

(推论-1)这个推论告诉我们,我们可以把算法分成两部分,按B-1方法求1到 N1/2 + 1部分的素数(Y1),在把N1/2 + 1到N的数求模一次求素数(而不是遍历素数表),得到Y2,Y1+Y2就是全部素数表。

///<summary>///平方法(算法B-3)///</summary>///<returns></returns>public static int[] GetPrimes3(int maxValue){int range = (int)Math.Sqrt(maxValue);// 素数列表List<int> primeList = new List<int>();int current = 3;// 从3开始的奇数列for (; current <= range; current += 2){// 判断是否为素数bool isPrime = true;foreach (int prime in primeList){if (current % prime == 0){// 若为合数,进入下一数的判断isPrime = false;break;}}if (isPrime){// 加入素数列表primeList.Add(current);}}List<int> primeList2 = new List<int>();// 从3开始的奇数列for (; current <= maxValue; current += 2){// 判断是否为素数bool isPrime = true;foreach (int prime in primeList){if (current % prime == 0){// 若为合数,进入下一数的判断isPrime = false;break;}}if (isPrime){// 加入素数列表primeList2.Add(current);}}// 把2补入primeList.Insert(0, 2);primeList.AddRange(primeList2);return primeList.ToArray();}这样的修改将使运行效率提高了近100倍。

6,利用筛法进行更高级的优化(算法B-4)对于上述用例,主要对19980个素数的二次循环上,在从N1/2到N之间的数中,进行log(M1)的循环(基数是623)和求模。

所以这个算法的复杂度是O(log(M1)*(N- N1/2))。

相关主题