一种快速打印前m个素数的算法

前m个素数是指从2开始的m个素数,比如前3个素数是:2,3,5;前10个素数是:2,3,5,7,11,13,17,19,23,29。
要找出前m个素数,很容易想到的算法是从2开始依次判断每一个整数是否是素数,直到找到m个素数。首先涉及到判断一个给定的数n是否为素数,其最简单的算法是,依次判断所有大于1且小于n的整数中是否存在能被n整除的数i,若存在则n不是素数,不存在则n为素数。代码实现如下:

//算法1
int isPrimeNumber(long n)
{
    
    
	for(int i = 2; i < n; i++)
	{
    
    
		if(n % i == 0)
			return 0;  //n不是素数
	}
	return 1;  //n是素数
}

该算法最简单明了,但有很大的缺点,判断次数多,冗余操作多,时间复杂度为O(n)。

实际上,算法1中n除以i得到的商小于i后,依然没有出现整除,则之后的整数已无需判断,就可以判定n为素数。因此对该算法进行一个简单的优化,将for(int i = 2; i < n; i++)改为for(int i = 2; i <= n/i; i++),所得到的新算法记为算法2,其冗余的判断整除的操作减少很多,时间复杂度减少到O(√n)。

但是该算法依然存在冗余判断整除操作,例如n已经无法整除2和3,那么显然也无法整除6,但该算法依然会对6进行整除判断。若将算法中的这类冗余操作完全去除,则运算速度将会进一步加快。那么有没有一种算法能做到没有冗余呢?

我们已知在数学上判断素数的方法:试除法,用从2开始的各个质数依次去除n,如果存在某一个质数整除,那么n不是质数;若不能整除,一直尝试到商小于除数,那么n是一个质数。这个方法没有冗余判断操作。

在打印前m个素数的算法中,可以使用一个数组来记录已经找到的质数。前几个素数已经为人们所熟知,无需再进行计算,因此可以把它们提前记录在这个数组中,比如可以将前8个素数2,3,5,7,11,13,17,19预先填入数组,后面找到的其他素数依次填入其中。将该算法记为算法3。以算法3为基础,得到打印前m个素数的C代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

#define MAX_SIZE  10000   //最多记录的素数个数

long primeNumberArray[MAX_SIZE] = {
    
    2, 3, 5, 7, 11, 13, 17, 19}; 
//初始化数组,填入前8个熟知的素数
int  currentPrimeNumber = 8;  //记录数组中已有的素数个数

//判定一个给定的数n是否为素数,若是返回1,不是返回0,出错返回-1
int isPrimeNumber(long n)
{
    
    
	long index = 0;
    for ( index = 0; index < currentPrimeNumber; index++)
	{
    
    
		if ( 0 == n % primeNumberArray[index] )
            return 0;  //n不是素数
		else if ( primeNumberArray[index] > (n/primeNumberArray[index]))	
		    return 1;  //n是素数
	} 
    return -1;
}

int main()
{
    
    
    int m;
    int result = 0, i = 0;
    long currentNumber = 21; //要判断的数,数组记录到19,从21开始

	printf("number of prime numbers to print: ");
	scanf("%d", &m);
	printf("\n");
   
    while (currentPrimeNumber < m)
	{
    
    
		result = isPrimeNumber(currentNumber);
		if (1 == result) //找到一个新的素数,记录到数组中
        {
    
    
			primeNumberArray[currentPrimeNumber] = currentNumber;
            currentPrimeNumber++;
		}
        else if (-1 == result)
        {
    
    
			printf("Fatal error occur!\n");
		}
		currentNumber += 2; //大于2的偶数均为和数,因此递增2
	}
    
    //打印前m个素数
	printf("Index     Prime Number\n");
    for (i = 0; i < m; i++)
		printf("%10d %ld\n",i + 1, primeNumberArray[i]);

	return 0;
}

为了验证这个无冗余算法的性能优势,我们在该程序中的while循环前后记录时间点,用以计算该程序耗时,以微秒为单位,更改后的main函数如下:

int main()
{
    
    
    int m;
    int result = 0, i = 0;
    long currentNumber = 21;
	struct timeval start, end;
	struct timezone tz;
    long timeelapse = 0;

	printf("number of prime numbers to print: ");
	scanf("%d", &m);
	printf("\n");

    gettimeofday(&start, &tz);  //开始计时
    while (currentPrimeNumber < m)
	{
    
    
		result = isPrimeNumber(currentNumber);
		if (1 == result)
        {
    
    
			primeNumberArray[currentPrimeNumber] = currentNumber;
            currentPrimeNumber++;
		}
        else if (-1 == result)
        {
    
    
			printf("Fatal error occur!\n");
		}
		currentNumber += 2;
	}
    gettimeofday(&end, &tz);  //结束计时
    
	printf("Index     Prime Number\n");
    for (i = 0; i < m; i++)
    {
    
    
		printf("%10d %ld\n",i + 1, primeNumberArray[i]);
	}	
	
	//计算并打印耗时,单位微秒
    timeelapse = (end.tv_sec - start.tv_sec)*1000*1000 + end.tv_usec - start.tv_usec;
    printf("timeelaspe = %ld\n", timeelapse);
    
	return 0;
}

在Ubuntu虚拟机上将函数isPrimeNumber(long n)依次替换为算法1,2和3,在打印前1000个、前5000个和前10000个素数时重复运行5次,观测时间耗费(微秒)取平均值,统计得到如下表格:

打印个数 1000 5000 10000
算法1 40589 1155341 4978551
算法2 1583 23223 71557
算法3 902 7133 17284

根据统计结果可以明显看出,算法1耗时最长,没有实用性,算法3在所有情况下的用时最少,效率最高。当打印素数的个数逐渐增加时,算法3相对于算法2的优势越来越大。但是算法3的使用也有一定局限性,其能判定的最大整数不能超过数组中最后一个素数的平方,并且其需要保存所得到的素数列表,因此比前两个算法占用更多的内存空间。

猜你喜欢

转载自blog.csdn.net/ClarkCC/article/details/108303121