最简单的筛素数法方法就是从2开始,将所以2的倍数去掉,然后从3开始,将3的倍数去掉。根据这样很容易写出代码,下面代码就是是筛素数法得到100以内的素数并保存到primes[]数组中。
//by MoreWindows( http://blog.csdn.net/MoreWindows )
const int MAXN = 100;
bool flag[MAXN];
int primes[MAXN / 3], pi;
void GetPrime_1()
{
int i, j;
pi = 0;
memset(flag, false, sizeof(flag));
for (i = 2; i < MAXN; i++)
if (!flag[i])
{
primes[pi++] = i;
for (j = i; j < MAXN; j += i)
flag[j] = true;
}
}
可以看出这种会有很多重复访问,如在访问flag[2]和flag[5]时会各访问flag[10]一次。因此最好想方法来减少这种重复访问,让flag[]数组的每个元素只被访问一次。可以这样考虑——简单的筛素数法是利用一个素数的倍数必须不是素数,同样任何一个数与其它所有素数的乘积必然也不是素数(这是因为每个合数必有一个最小素因子)。
为了试验这种想法,先用2到10之间的数来验证下。
2,3,4,5,6,7,8,9,10 初始时所以flag都是无标记的。
第一步 访问2,flag[2]无标记所以将2加入素数表中,然后将2与素数表中的所有数相乘得到的数必定不是素数,2*2=4因此标记flag[4]。
2,3,4,5,6,7,8,9,10
第二步 访问3,flag[3]无标记所以将3加入素数表中,将3与素数表中的所有数相乘得到的数必定不是素数,3*2=6,3*3=9因此标记flag[6]和flag[9]。
2,3,4,5,6,7,8,9,10
第三步 访问4,flag[4]有标记所以4不加入素数表中,将4与素数表中的所有数相乘得到的数必定不是素数, 4*2=8,4*3=12因此标记flag[8]。
2,3,4,5,6,7,8,9,10
第四步 访问5,flag[5]无标记所以将5加入素数表中,将5与素数表中的所有数相乘得到的数必定不是素数,5*2=10,5*3=15因此标记flag[10]。
2,3,4,5,6,7,8,9,10
第五步 访问6,flag[6]有标记所以6不加入素数表中,将6与素数表中的所有数相乘得到的数必定不是素数, 6*2=12,6*3=18,6*5=30。
2,3,4,5,6,7,8,9,10
后面几步类似,代码不难写出:
//by MoreWindows( http://blog.csdn.net/MoreWindows )
const int MAXN = 100;
bool flag[MAXN];
int primes[MAXN / 3], pi;
void GetPrime_2()
{
int i, j;
pi = 0;
memset(flag, false, sizeof(flag));
for (i = 2; i < MAXN; i++)
{
if (!flag[i])
primes[pi++] = i;
for (j = 0; (j < pi) && (i * primes[j] < MAXN); j++)
flag[i * primes[j]] = true;
}
}
这份代码对不对了?仔细回顾下分析过程,可以发现有些数据还是被访问多次了,这当然不是我们希望的结果,我们的要求是让每个合数仅被它的最小素因子筛去一次。比如12,它的最小素因子是2,所以就只应该被在计算6*2时去访问,而且不应该在计算4*3时去访问,同理18也只应该被在计算9*2时去访问,而且不应该在计算6*3时去访问。
找到原因后,再来思考如何解决。6*3不行而9*2可以了,是因为6是2的倍数,所以在计算6*2之后就不能再将6与比2大的素数相乘,这些相乘的结果必定会导致重复计算。因此对于任何数来说,如果它如果是该素数的倍数那么它就不能再与素数表中该素数之后的素数相乘了,如9是3的倍数,所以在9*3之后就不能再去用计算9*5了。因此在代码中再增加一行判断语句:
//by MoreWindows( http://blog.csdn.net/MoreWindows )
const int MAXN = 100;
bool flag[MAXN];
int primes[MAXN / 3], pi;
void GetPrime_2()
{
int i, j;
pi = 0;
memset(flag, false, sizeof(flag));
for (i = 2; i < MAXN; i++)
{
if (!flag[i])
primes[pi++] = i;
for (j = 0; (j < pi) && (i * primes[j] < MAXN); j++)
{
flag[i * primes[j]] = true;
if (i % primes[j] == 0) //这句保证每个非素数只被筛去一次
break;
}
}
}
想知道这二种筛素数法方法的区别吗?现在对求2到1亿之间的素数进行测试,看看区别到底会有多大,测试代码如下:
// 普通的筛素数方法与改进之后的效率对比
// by MoreWindows( http://blog.csdn.net/MoreWindows )
#include <stdio.h>
#include <memory.h>
#include <time.h>
#include <math.h>
const int MAXN = 100000000;
bool flag[MAXN];
int primes[MAXN / 3], pi;
// 利用对每个素数的倍数必定不是素数来筛选
void GetPrime_1()
{
int i, j;
pi = 0;
memset(flag, false, sizeof(flag));
for (i = 2; i < MAXN; i++)
if (!flag[i])
{
primes[pi++] = i;
for (j = i; j < MAXN; j += i)
flag[j] = true;
}
}
// 利用了每个合数必有一个最小素因子来筛选
void GetPrime_2()
{
int i, j;
pi = 0;
memset(flag, false, sizeof(flag));
for (i = 2; i < MAXN; i++)
{
if (!flag[i])
primes[pi++] = i;
for (j = 0; (j < pi) && (i * primes[j] < MAXN); j++)
{
flag[i * primes[j]] = true;
if (i % primes[j] == 0)
break;
}
}
}
int main()
{
printf(" 在%d的数据量下普通的筛素数方法与改进之后的效率对比\n", MAXN);
printf(" by MoreWindows( http://blog.csdn.net/MoreWindows ) -- --\n\n");
clock_t clockBegin, clockEnd;
clockBegin = clock();
GetPrime_1();
clockEnd = clock();
printf("普通的筛素数方法\t%d毫秒\n", clockEnd - clockBegin);
clockBegin = clock();
GetPrime_2();
clockEnd = clock();
printf("改进的筛素数方法\t%d毫秒\n", clockEnd - clockBegin);
return 0;
}
测试结果如图所示:
可以看出,效率有4倍之差。改进还是比较可观。有兴趣的同学可以参考下一篇《位操作基础篇之位操作全面总结》所讲到的空间压缩技巧来将改进后的筛素数法方进行空间压缩。
文章最后作下小小总结:
1.普通的筛素数的原理是一个素数的倍数必须不是素数。
2.改进的筛素数的原理是每个合数必有一个最小素因子,根据每个最小素因子去访问合数就能防止合数被重复访问。
另外,筛素数法还有很多种改进手段,在数学论坛上可以去研读一下,本文就不再深究了。
分享到:
相关推荐
首先,寻找素数的基本方法包括“试除法”和更高效的算法如“埃拉托斯特尼筛法”。试除法是最直观的方法,即对每个待判断的数字n,从2到√n遍历所有可能的因子,如果发现因子,则n不是素数。这种方法虽然简单,但对于...
- **Eratosthenes筛法**:一种经典的消除合数的方法,通过从2开始依次筛掉每个质数的倍数,找到所有小于等于给定数n的质数。优化后的Eratosthenes筛法仅处理每个合数的第一个倍数。 - **线性筛法**:效率更高的筛...
在实际应用中,为了提高效率,通常会采用更优化的算法,如埃拉托斯特尼筛法(Sieve of Eratosthenes),它可以在较短的时间内找出一个大范围内所有的质数。尽管这种方法的实现稍微复杂一些,但对于处理大量数据时,...
MATLAB中寻找素数的方法通常基于筛法,如埃拉托斯特尼筛法。用户可以自定义函数,例如,创建一个`isprime()`函数,通过检查一个数是否能被小于其平方根的所有数整除来判断是否为素数。此外,`primes()`函数则直接...
除了以上的方法,还有其他更高级的算法来高效地找出一定范围内的所有质数,如埃拉托色尼筛选法(Sieve of Eratosthenes)和线性筛法(Linear Sieve)。埃拉托色尼筛选法是一种经典的算法,通过排除每个质数的倍数来...
23.筛法求素数 24.指针数组与数组指针 25.const修饰符 26.break_continue_return 27.数学运算 28.表达式特性 29.按位运算 30.数据类型转换 31.c串入门 32.进制问题 33.加密解密入门 34.c串库函数 35.c风格输入输出 ...
5. 图论与组合优化:Eratosthenes筛法是寻找质数的一种高效算法,它利用数论性质筛选掉非质数,常用于解决组合优化问题。 6. 数学证明:费马大定理和黎曼猜想等著名数论问题的探讨,启发了新的算法思想和证明技术,...
1. **取所有质数**:使用筛选法(埃拉托斯特尼筛法)来找到一定范围内的所有质数,通过标记非质数来筛选出质数。 2. **最小公倍数**:通过循环检验,不断将较小的数与较大的数的余数进行比较,直至余数为0,找到的...
12. 质数生成:在指定范围内找到所有素数,可以使用埃拉托斯特尼筛法。 13. 打印乘法口诀表:涉及二维数组和嵌套循环。 14. 最大公约数:实现欧几里得算法,需要理解整数除法和取余操作。 15. 2的幂值:找到小于n的...
- 第四个题目使用了著名的埃拉托斯特尼筛法来找出2到80之间的所有素数。首先,初始化一个数组,将所有偶数标记为非素数,然后从3开始,将每个素数的倍数标记为非素数。最后,未被标记的数组元素即为素数。 5. **...
13. **筛法找素数**:使用埃拉托斯特尼筛法找出一定范围内的所有素数。 14. **挑选排序与冒泡排序**:两种简单的排序算法实现。 15. **矩阵操作**:处理矩阵元素,如求对角线元素之和或边界元素之和。 16. **数组...
12. 素数生成:使用筛法或其他素数检测算法,输出指定范围内的素数并格式化输出。 13. 打印乘法口诀表:使用嵌套循环,按照一定格式输出乘法口诀。 14. 最大公约数计算:实现欧几里得算法,通过不断求余数直至余数...
- **埃拉托斯特尼筛法**:一种用于找出一定范围内所有素数的高效方法,通过不断排除已知素数的倍数来筛选出素数。 - **Miller-Rabin素性检验**:这是一种概率性测试,对于较大的数,正确性非常高,但不能保证绝对...
C语言中,我们可以用埃拉托斯特尼筛法来解决,这是一种效率较高的求素数的方法。通过创建一个数组并逐步排除非素数,最终得到所有小于某个上限的素数。 除了这些,这100个经典算法可能还包括排序算法(如冒泡排序、...
这段Java代码可能会使用一种常见的算法,例如埃拉托斯特尼筛法(Sieve of Eratosthenes),或者更简单的遍历和除法检查方法。对于后者,程序会遍历2到100的每个数,对每一个数i,检查是否有任何小于i且大于1的数能...
求质数的程序可能采用筛法(如埃拉托斯特尼筛法)或其他算法。它会涉及循环和条件语句来判断一个数是否为质数,也可能包含性能优化,如只检查到其平方根。 这些小程序覆盖了Java编程的基础和进阶概念,包括面向...
* 简单数学题:欧几里德算法求最大公约数、筛法求素数、康托展开、逆康托展开、同余定理、次方求模等 * 计算几何初步:三角形面积、三点顺序 * 算法设计与分析:学会简单计算程序的时间复杂度与空间复杂度、贪心算法...
8. 素数之和:理解素数定义,从2开始遍历到m,通过素数判断算法(如埃拉托斯特尼筛法)找出所有素数并累加。 9. 求最大值和最小值的差:找到数组中的最大值和最小值,然后相减,涉及数组遍历和极值查找。 10. 找...
- **素数与合数**: 如何判断一个数是否为素数(埃拉托斯特尼筛法)、素数个数的估算等。 - **同余方程**: 同余方程的解法、中国剩余定理的应用等。 - **模运算**: 模运算的基本性质、逆元的概念及其求解方法(费马小...
- **埃拉托斯特尼筛法(Sieve of Eratosthenes)**:这是一种古老的算法,用于找出指定范围内的所有素数。它通过逐步排除非素数来生成素数列表。 - **试除法**:检查一个数n是否为素数,可以尝试除以小于等于√n的...