C语言素数判断以及打印素数表

引言 

素数判断与打印素数表,是一道经典题目,相信小伙伴曾经学习C语言的时候肯定有所接触,但是你有没有真正理解这题的奥秘呢?不妨跟着我来一起思考

素数判断

方法1:试除法

根据素数定义,只能被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:6倍原理法

素数有一个性质:除了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的旁边),所以才需加上代码中第三部分的判断,如上图。

此方法比试除法效率大幅度提高。

打印素数表

方法1:试除法与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;
}

方法2:试除法(main函数中直接判断并打印)

在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;
}

方法3:筛选法

这个方法打印素数表的效率,远远高于试除法(哪怕已经优化多次)

原理:所有自然数中,从前往后,先判断该数是否为素数,如果是,则将往后它的倍数都剔除掉,然后再重复以上操作。比如,第一个判断到的质数为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倍原理法封装为一个函数来实现功能,又比如要求打印素数表,则可选取筛选法高效打印。这些思想才是我们应该透过题目与语言本身去学习与应用的,是精华所在。

你可能感兴趣的:(C语言,c语言)