素数求解及其优化

Posted _NiuLi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了素数求解及其优化相关的知识,希望对你有一定的参考价值。

题目一:

    请实现一个函数,对于给定的整型参数N,依次打印出小于N的素数。



解法一:试除法


由素数的定义我们很自然的会想到如下代码:

#include <stdio.h>

void print_prime(int n)

	int i=0;
	for(i=2;i<=n;i++)
	
		int j=0;
		for(j=2;j<i;j++)
		
			if(0==i%j)
				break;	
		
		if(j==i)
			printf("%d\\t",i);
	


int main()

	int num=0;
	scanf("%d",&num);
	print_prime(num);	
	return 0;

      在上面的代码中,我们看到在判断素数时一直从2试除到n-1。这里从n/2之后的数到n-1的试除显然是多余的,比如某个数不能被3整除,必然不能被6整除。



优化1:


    试除的范围优化到[2,n/2],这样一下子就将工作量减少了一半,代码如下:

void print_prime(int n)

	int i=0;
	for(i=2;i<=n;i++)
	
	        int j=0;
		for(j=2;j<=i/2;j++) //修改部分
		
			if(0==i%j)
				break;	
		
		
		if(j==(i/2+1))  //修改部分
			printf("%d\\t",i);
	


    既然能将试除范围优化到[2,n/2],那么这个范围是不是还能优化呢?答案是可以的,在[2,n/2]这个范围里(√n,n/2]的试除也是多余的。因为因数是成对出现的,比如16可分解为:1和16 、2和8、4和4、8和2、16和1。这些因数里必然有一个小于等于4。所以只需试除小于等于√n的数就可以了。



优化2:

      试除范围优化为[2,√n],代码如下:


#include <stdio.h>
#include <math.h>

void print_prime(int n)

	int i = 0;
	for (i = 2; i <= n; i++)
	
		int j = 0;
		for (j = 2; j <= sqrt(i); j++) //修改部分
		
			if (0 == i%j)
				break;
		
		
		if (j >sqrt(i))         //修改部分
			printf("%d\\t", i);
	


int main()

	int num = 0;
	scanf("%d", &num);
	print_prime(num);
	return 0;


    上面所有的代码在找素数的时候是从2到n,在这个范围内除了2之外的偶数都不是素数,所以我们可以跳过这些偶数。

    还有试除范围内除了2之外的偶数也是没有必要的,因为如果不能被2整除,必然不能被大于2的偶数整除。



优化3:

    寻找素数时跳过偶数、试除范围跳过除2之外的偶数。代码如下:


void print_prime(int n)

	int i = 0;
	if (n >= 2)         //修改部分
		printf("%d\\t", 2);
		
	for (i = 3; i <= n; i+=2)   //修改部分
	
		int j = 2;
		if(0==i%j)  //修改部分
		    break;
		    
		for (j = 3; j <= sqrt(i); j+=2) //修改部分
		
			if (0 == i%j)
				break;
		
		
		if (j >sqrt(i))
			printf("%d\\t", i);
	


    其实在上面的代码中,试除范围内的一些数也是不必要的。比如判断101是否为素数时,要分别试除小于10的2和所有奇数,即2、3、5、7、9,其实对9的试除是不必要的。即对所有的非素数的试除是不必要的,因为非素数必然可分解为比它小的素数的乘积,既然它的质因数不能整除某个数,这个数必然也不能。故试除的范围可缩小到小于等于√n的所有素数。



优化4:

    只试小于√n的素数

     

    那么问题来了,要试除这些素数时必然要将前面求出的素数保存起来,开辟多大一块空间合适呢?因为n的大小未知,所以无法确定开辟多少空间。这里可以使用动态内存开辟空间,当空间不够时使用realloc()追加空间。

代码实现如下:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define LEN 10 //每次开辟空间的大小

void  print_prime(int n)

	int *p = (int*)calloc(sizeof(int),LEN);
	if (p == NULL)
	
		printf("out of memory !\\n");
		exit(EXIT_FAILURE);
	

	int i = 0, j = 1;
	int count = 1;

	if (n >= 2)  //第一个素数,输出并保存起来
	
		printf("%d\\t", 2);
		p[0] = 2;
	

	for (i = 3; i <= n; i += 2)
	
		int k = 0;
		while (p[k]>0 && p[k] <= sqrt(i))   //试除保存的素数
		
			if (i%p[k] == 0)
				break;
			k++;
		

		if (!p[k] || (p[k] > sqrt(i)))  //将新求出的素数保存起来
		
			printf("%d\\t", i);
			if (j >= (LEN*count))
			
				count++;
				p = realloc(p, sizeof(int)*LEN*count);
				if(p==NULL)
				        exit(EXIT_FAILURE);
			
			p[j] = i;
			j++;
		

	
	free(p);


int main()

	int num = 0;
	scanf("%d", &num);

	print_prime(num);

	return 0;




解法二:筛法


    这种方法求素数的思想就是,不断筛去最小的数的倍数。这个最小的数必然是素数。


    比如最小的素数是2,去掉所有2的倍数;接下来最小的数是3,3就是素数,去掉所有的3的倍数;依次类推,直到最小的数小于等于√n为止。为什么是√n呢? 在上面的试除法中讲到只要试除小于等于√n的所有素数即可判断出小于等于n的所有素数,这里同样适用,只要去掉所有的小于等于√n的所有数的倍数,剩下的数就是小于等于n的所有素数。


代码如下:


#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void print_prime(int n)

	int *arr=(int*)malloc(sizeof(int)*n);
	if (arr == NULL)
	
		printf("out of memory !\\n");
		exit(EXIT_FAILURE);
	
	int i = 0, j = 0;
	for (i = 0; i < n - 1; i++) //初始化数组[2,n]
	
		arr[i] = i + 2;
	
	while (arr[j] <= sqrt(n))  //除数的范围
	
		for (i = j + 1; i < n - 1; i++)
		
			if (arr[i] % arr[j] == 0)//筛去arr[i]的倍数
				arr[i] = 0;
		
		j++;
		while (!arr[j])  //确定最小数
			j++;
	

	for (i = 0; i < n - 1; i++)    //打印素数
	
		if (arr[i])
			printf("%d\\t", arr[i]);
	
	free(arr);


int main()

	int num = 0;
	scanf("%d", &num);
	print_prime(num);
	return 0;


    上面的代码有一个很明显的缺陷就是开辟的空间过大,如何来解决这个问题呢?

    上面代码所开辟的空间为int型,占用空间太多,我们可以构造一个bool型数组,以下标来存储数据,这样就节省了75%的空间。



优化1:

    构造bool型数组,以下标来存储数据,每个数只占一个字节。


代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>


void print_prime(int n)

	bool *arr=(bool*)malloc(sizeof(bool)*n);
	if (arr == NULL)
	
		printf("out of memory !");
		exit(EXIT_FAILURE);
	
	int i = 0, j = 2;

	for (i = 0; i <= n; i++)//初始化数组为true
	
		arr[i] = true;
	

	while (j <= sqrt(n))    //除数的范围
	
		for (i = j + 1; i <= n; i++)
		
			if (i%j == 0)
				arr[i] = false;   //筛除
		

		j++;
		while (!arr[j])    //寻找最小数
			j++;
	

	for (i = 2; i <= n; i++)  //打印素数
	
		if (arr[i])
			printf("%d\\t", i);
	
	free(arr);


int main()

	int num = 0;
	scanf("%d", &num);
	print_prime(num);

	return 0;


    上面的代码使用bool类型占一字节来存储数据,想到这里,我们是不是可以用位来存储数据,一个字节有8位,每位节用bool值来表示这样空间利用率会大大的提高。



优化2:

    构造定长的byte数组,用bit位存储数据

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>


//将ch的第position位  置0
unsigned char set_bit_0(unsigned char ch, int position)

	return ch  &  (~  ( (unsigned char)pow(2,  position)  )  );



//找arr指向的内存第position个位起,第一个不为0的比特位
int check_bit_1(int num,const unsigned char *arr, int pos)  

	
	while (pos<= num)
	
		if ((  (arr[pos / 8] >> (pos % 8)   ) & 1) == 1)
			return pos;
		pos++;
	
	return 0;



//求解素数
void print_prime(int n)

	unsigned char *arr = (unsigned char*)malloc(sizeof(unsigned char)*n / 8+1);
	if (arr == NULL)
	
		printf("out of memory !\\n");
		exit(EXIT_FAILURE);
	
	
	int i = 0,j=2;
	
	for (i = 0; i < n/8+1; i++) //将bit数组置为全1
	
		arr[i] = 0xff;
	

	while (j <= sqrt(n)) //筛除范围
	
		for (i = j + 1; i <= n; i++)
		
			if (i%j == 0) //将非素数对应的位 置0
				arr[i / 8] = set_bit_0( arr[i / 8] , i % 8);
		
		
		j++;
		j = check_bit_1(n,arr, j);
		if (j == 0)
			break;
	
	
	for (i = 2; i <= n; i++)   //打印素数
	
		i = check_bit_1(n,arr, i);
		if (i == 0)
			break;
		printf("%d\\t", i);

	




int main()

	int num = 0;
	scanf("%d", &num);
	print_prime(num);
	return 0;



题目二:

        从小到大依次打印N个素数


解法一:试除法


        经过题目一的求解优化,这里直接给出试除法优化的终极版:试除保存起来的素数。



#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void print_N_prime(int num)

	int count = 0;
	int i = 3,j=0,flag=2;
	int *arr = calloc(num,sizeof(int));
	
	if(arr==NULL)
	
	        printf("out of memory !\\n");
	        exit(EXIT_FAILURE);
	
	
	arr[0] = 2; 
	count = 1;    //统计素数的个数
	
	while (count<num)
	

		for (j = 0;  arr[j] <= sqrt(i) && arr[j] ;  j++)  //试除判断素数
		
			if (i%arr[j] == 0)
				break;
		

		if (arr[j] > sqrt(i))  
		
			arr[count]=i;
			count++;
		

		i++;
	

	for (i = 0; i < count; i++)  //打印素数
	
		printf("%d\\t", arr[i]);
	


int main()

	int n = 0;
	scanf("%d", &n);
	
	print_N_prime(n);

	return 0;


解法二:筛法


    筛法需要容器,这个容器要多大呢?由素数定理可以近似求出素数的分布范围。如0~x中有x/lnx个素数,反推即可求出n个素数的分布范围,由于这只是近似,把容器再扩大30%,应该足够了。

    求出范围后解法与题目一类似,只需在输出素数时控制输出个数即可。





    注:本文借鉴了program_think在CSDN的博客,求解素数的N种境界(N>10)

http://blog.csdn.net/program_think/article/details/7032600


以上是关于素数求解及其优化的主要内容,如果未能解决你的问题,请参考以下文章

素数序列的生成及其应用(采用了新学的更高效的算法:布尔标记法 + 倍数淘汰法)

素数的筛法

暴力求解

素数筛时间空间优化

素数筛时间空间优化

求100以内素数的5中基本方法及其优化