素数判断与打印素数表,是一道经典题目,相信小伙伴曾经学习C语言的时候肯定有所接触,但是你有没有真正理解这题的奥秘呢?不妨跟着我来一起思考
根据素数定义,只能被1与本身整除,如果判断n可以整除小于n的数,则不为素数,返回0;如果试除完所有小于n的数都不满足判断,则n一定为质数,返回1
int is_prime(int n)
{
//返回1表示素数,返回0表示非素数
int i = 0;
for (i = 2; i < n; i++)
{
if (n % i == 0)
{
return 0;
}
}
return 1;
}
该方法还可进一步优化。
因为一个数n如果不为素数,则可以因式分解为两个数,如n=a*b,则其中至少有一个数小于n的开平方,如16=2*8,其中2便小于根号16(也就是4)。
那么,根据这个原理,n来试除i时,i只要小于根号n即可。因为只要n不为素数(必定能分解为两个数),那么在试除小于根号n的数字里,便一定会满足能整除的判断;反之,如果n为素数,只要根号n前的数字都不能被n整除,那么后面也没有数字会满足。
#include
int is_prime(int n)
{
//返回1表示素数,返回0表示非素数
int i = 0;
//sqrt函数用来开根号
for (i = 2; i < sqrt(n); i++)
{
if (n % i == 0)
{
return 0;
}
}
return 1;
}
这里我们开根号运用了sqrt库函数,要引用
这一步优化,将使程序的效率大大提升,比如n为100w,则最多只用循环10w次
素数有一个性质:除了2和3以外,其余素数一定在6的倍数的两侧
证明:所有数可表示为6k,6k+1,6k+2,6k+3,6k+4,6k+5 (k=0,1,2……)
而6k,6k+2=2(3k+1),6k+3=3(2k+1),6k+4=2(3k+2),均为偶数,则剔除
剩余6k+1,6k+5,因此证明完毕
那么我们就可以用这个性质来进行更高效率的判断
int is_prime(int n)
{
//2和3单独讨论
if (n == 2 || n == 3)
{
return 1;
}
//不在6的倍数两侧的,肯定不为素数
if ((n % 6) != 1 && (n % 6) != 5)
{
return 0;
}
//在6的倍数两侧也可能不为素数
int i = 0;
for (i = 5; i <= sqrt(n); i += 6)
{
if (n % i == 0 || n % (i + 2) == 0)
{
return 0;
}
}
//排除所有可能,剩余的就是素数
return 1;
}
值得注意的是,在6的倍数两侧也可能不为素数,比如25(在24的旁边),但是可以整除5(在6的旁边),所以才需加上代码中第三部分的判断,如上图。
此方法比试除法效率大幅度提高。
上述两种素数判断方法写成函数的形式,可以直接用循环加判断的方式打印
试除法:
#include
#include
int is_prime(int n)
{
//返回1表示素数,返回0表示非素数
int i = 0;
for (i = 2; i < sqrt(n); i++)
{
if (n % i == 0)
{
return 0;
}
}
return 1;
}
int main()
{
//打印1000 - 2000的素数
int i = 0;
for (i = 1000; i <= 2000; i++)
{
if (is_prime(i) == 1)
{
printf("%d ", i);
}
}
return 0;
}
6倍原理法:
#include
#include
int is_prime(int n)
{
if (n == 2 || n == 3)
{
return 1;
}
if ((n % 6) != 1 && (n % 6) != 5)
{
return 0;
}
int i = 0;
for (i = 5; i < sqrt(n); i += 6)
{
if (n % i == 0 || n % (i + 2) == 0)
{
return 0;
}
}
return 1;
}
int main()
{
//打印1000 - 2000的素数
int i = 0;
for (i = 1000; i <= 2000; i++)
{
if (is_prime(i) == 1)
{
printf("%d ", i);
}
}
return 0;
}
在main函数中直接实现,思路与前面大致相同:
1.先用循环生成要打印的数的区间,如1000 - 2000
2.再嵌套一层循环,用于判断每个数i是否能整除比i小的数
3.再用sqrt函数进行优化
有几个与之前不同的小细节:
1.生成数字区间的时候,只生成奇数,这也是一步小优化(因为素数不可能为偶数)
2.设立flag来表示i是否为素数,1为素数,0为非素数(这一点与函数返回1和0目的是一样的)
3.当判断i整除j成立时,即i不为素数,加上break,立马跳出当前循环,避免浪费时间,进一步优化(因为已经判断i不为素数,继续循环判断下去没有意义)
4.当循环素数判断结束后,再判断flag是否为1,如果为1,即循环中没有将flag置为0,说明i为素数,则将i打印
#include
#include
int main()
{
int i = 0;
int count = 0;
for (i = 1001; i <= 2000; i += 2)//只判断奇数
{
//产生1000-2000内的奇数
//假设i为素数
int flag = 1;
int j = 0;
for (j = 2; j <= sqrt(i); j++)//i能分解成两个因子,其中至少有一个因子小于根号i
{
//只要i能整除根号i前的数,说明根号i后也有一个数能被整除
//如果没有,则找不到能被整除的数,即i为素数
if (0 == (i % j))
{
flag = 0;//则此时i不为素数
break;
}
}
if (1 == flag)
{
printf("%d ", i);
count++;
}
}
printf("\ncount=%d\n", count);
return 0;
}
这个方法打印素数表的效率,远远高于试除法(哪怕已经优化多次)
原理:所有自然数中,从前往后,先判断该数是否为素数,如果是,则将往后它的倍数都剔除掉,然后再重复以上操作。比如,第一个判断到的质数为2,则将2打印出来,再将4,6,8,12……全部剔除,第2个判断到的质数为3,则将3打印出来,再将6,9,12,15……全部剔除,依此类推,最后剩下的一定全部是质数。
代码如下:
#include
int main()
{
int i = 0;
int count = 0;
int arr[2000] = { 0 };
for (i = 2; i < 2000; i++)
{
arr[i] = 1;
}
//去除2,3,5……的倍数
for (i = 2; i < 2000; i++)
{
if (arr[i] != 0)
{
if ((i >= 1000) && (i <= 2000))//设置打印区间
{
count++;
printf("%d ", i);
}
int j = 0;
for (j = i + i; j < 2000; j += i)
{
arr[j] = 0;
}
}
}
printf("\ncount=%d\n", count);
return 0;
}
分析:因为我们要同时记录数的两种状态(是否被剔除,数的值),因此我们用数组来记录
1.先将数组循环初始化,每个值赋予1,表示未被剔除,而下标表示数的值
2.接下来进入循环,从2开始,先打印,再嵌套一层循环来剔除其倍数(将数组的值赋成0,表示已被剔除)
3.再次循环判断时,已被剔除的数将不会进入后续过程,而是下一个素数进入,继续打印并剔除
此方法还可以进一步优化。
不知道聪明的你是否已经发现,在剔除的过程中,总会存在重复剔除的现象,比如2已经剔除了6,而3又剔除了一遍,这样就造成了浪费。所以我们可以把j循环的初始值修改一下,使其不会出现重复剔除的现象,那如何进行修改呢?
数学归纳法发现,将j从i+i改为i*i即可解决问题。比如,3从9开始剔除,则不会再重复剔除6;4从16开始,不会重复剔除8和12……
#include
int main()
{
int i = 0;
int count = 0;
int arr[2000] = { 0 };
for (i = 2; i < 2000; i++)
{
arr[i] = 1;
}
//去除2,3,5……的倍数
for (i = 2; i < 2000; i++)
{
if (arr[i] != 0)
{
if ((i >= 1000) && (i <= 2000))//设置打印区间
{
count++;
printf("%d ", i);
}
int j = 0;
for (j = i * i; j < 2000; j += i)//把+改成*,避免重复筛选
{
arr[j] = 0;
}
}
}
printf("\ncount=%d\n", count);
return 0;
}
一道题有多种思路与实现方式,关键是要学会优化的思想,选取最优解法,这是我们在学习中应该掌握的真正能力。同时,我们也应该学会根据场景与需求的不同,来选取合适的方法,比如要求判断一个数是否为素数,则可将高效率的6倍原理法封装为一个函数来实现功能,又比如要求打印素数表,则可选取筛选法高效打印。这些思想才是我们应该透过题目与语言本身去学习与应用的,是精华所在。