问题需求是求整数N以内的所有素数。算法很朴素,即每次拿待检验整数candidate和已求得的素数列表内的素数进行取模运算(candidate % primes[i] )即可,而且只需要拿小于等于根号candidate的素数去检验。如果整除,则candidate为合数,不是素数。
程序用C++实现。第一反应就用vector来保存素数表。实现以后考虑是不是每次求得一个素数都push_back进去效率会很低,所以试了试list(遍历只能用迭代器了,但vector用下标遍历很方便)和deque(其中分别尝试了下标和迭代器),结果vector完胜:
The quantity of primes less than 10000000 is:664579
The vector method takes 3.05714 seconds
The list method takes 5.97575 seconds
The deque method (using index) takes 31.1583 seconds
The deque method (using iterator)takes 10.5836 seconds
想了想确实,整个算法的遍历运算非常多(因为每次都要按顺序遍历素数表的前一部分),而插入元素只是到尾部,不存在删除,这样用vector再适合不过了!至于push_back,由于STL vector在实现上用了备用空间,每次push_back需要向内存里要空间而备用空间已用完时,STL会多要一倍,这样可以省不少内存分配的时间。所以算法运行时,随着vector的增大,需要重新分配内存的次数也会越来越稀少,vector相对于list和deque在内存分配速度上的劣势也越来越小。而list和deque迭代器遍历再怎么快,每次循环(待检验数++)里面总是要涉及迭代器对象的创建与销毁,速度上肯定不如一个Int做索引来得快。
有意思的是,deque的索引远不如迭代器快,而deque本身由于在实现上骑在vector与list二墙之间,反而在本算法中性能远不如二者。
我还想继续优化。因为vector的push_back在前几次尾部扩展发生最频繁,于是乎猜想预先定义好vector的长度,应该就可以减少尾部扩张的次数,甚至直接定义冗余长度,到最后再把多余的部分砍掉(erase函数)。
估计素数表长度我用了素数定理,估计值和实际值大约有1.1左右的误差,所以我把估计值分别乘以了1.1和1.2然后跑了一下vector实现的程序:
The quantity of primes less than 10000000 is:664579
The vector method takes 3.24956 seconds
The vector(1.1) method takes 2.81584 seconds
The vector(1.2) method takes 2.74588 seconds
不错,还是有优化的。(奇怪的是乘以1.1已经足够大了,为什么乘以1.2估计得更不准,却速度更快?)把输入翻10倍试试:
The quantity of primes less than 100000000 is:5761455
The vector method takes 60.5083 seconds
The vector(1.1) method takes 58.6634 seconds
The vector(1.2) method takes 58.9925 seconds
(这次输入的素数上限是上一次的十倍了,所以耗时多了很多倍)两次结果对比,预分配的办法节省时间的百分率减小了,表明当数据规模大增的时候,省下的内存分配的时间就不算多了。
附上算法(头文件未列出,需要在里面include必要的头文件):
//
// Prime.cpp
// OpenMP
//
// Created by Sheng Peng on 13-6-21.
// Copyright (c) 2013年 Sheng Peng. All rights reserved.
//
#include "Prime.h"
#include <math.h>
//向量法
vector<int> getPrimeVector(int n){
vector<int> primes;
if (n<2) return primes;//不存在小于2的素数
primes.push_back(2);//2是第一个素数
int square=2*2;//上限素数值的平方,如果待检验数大于它,那么要换一个更大的素数做上限
int upperlimit=0;//素数检验上限
for (int candidate = 3; candidate <= n; candidate++){
bool isPrime = true;
if (candidate>square){
upperlimit++;
square=primes[upperlimit] * primes[upperlimit];
}
for (int i=0; i<=upperlimit; i++){
int prime = primes[i];
if (candidate % prime == 0){
isPrime = false;
break;
}
}
if(isPrime){
primes.push_back(candidate);
}
}
return primes;
}
//向量法,估计素数表长度*1.1
vector<int> getPrimeVector_1_1(int n){
cout<<"预计个数"<<n/log(n)*1.1<<endl;
int pre_num=round(n/log(n)*1.1);
vector<int> primes(pre_num);
if (n<2) return primes;//不存在小于2的素数
primes[0]=2;//2是第一个素数
int square=2*2;//上限素数值的平方,如果待检验数大于它,那么要换一个更大的素数做上限
int upperlimit=0;//素数检验上限
int num=1;//素数个数
for (int candidate = 3; candidate <= n; candidate++){
bool isPrime = true;
if (candidate>square){
upperlimit++;
square=primes[upperlimit] * primes[upperlimit];
}
for (int i=0; i<=upperlimit; i++){
if (candidate % primes[i] == 0){
isPrime = false;
break;
}
}
if(isPrime){
primes[num]=candidate;
num++;
}
}
primes.erase(primes.begin()+num, primes.end());
return primes;
}
//向量法,估计素数表长度*1.1
vector<int> getPrimeVector_1_1(int n){
cout<<"预计个数"<<n/log(n)*1.2<<endl;
int pre_num=round(n/log(n)*1.2);
vector<int> primes(pre_num);
if (n<2) return primes;//不存在小于2的素数
primes[0]=2;//2是第一个素数
int square=2*2;//上限素数值的平方,如果待检验数大于它,那么要换一个更大的素数做上限
int upperlimit=0;//素数检验上限
int num=1;//素数个数
for (int candidate = 3; candidate <= n; candidate++){
bool isPrime = true;
if (candidate>square){
upperlimit++;
square=primes[upperlimit] * primes[upperlimit];
}
for (int i=0; i<=upperlimit; i++){
if (candidate % primes[i] == 0){
isPrime = false;
break;
}
}
if(isPrime){
primes[num]=candidate;
num++;
}
}
primes.erase(primes.begin()+num, primes.end());
return primes;
}
//列表法
list<int> getPrimeList(int n){
list<int> primes;
if (n<2) return primes;//不存在小于2的素数
primes.push_back(2);//2是第一个素数
int square=2*2;//上限素数值的平方,如果待检验数大于它,那么要换一个更大的素数做上限
list<int>::iterator upperlimit=primes.begin();//素数检验上限
for (int candidate = 3; candidate <= n; candidate++){
bool isPrime = true;
if (candidate>square){
upperlimit++;
square=(*upperlimit) * (*upperlimit);
}
for (list<int>::iterator i=primes.begin();i!=upperlimit;i++){
if (candidate % (*i) == 0){
isPrime = false;
break;
}
}
if(isPrime && (candidate % (*upperlimit) != 0)) primes.push_back(candidate);//由于循环检验遗漏了上限素数,故需要补上
}
return primes;
}
//双向队列法,用下标遍历
deque<int> getPrimeDeque_index(int n){
deque<int> primes;
if (n<2) return primes;//不存在小于2的素数
primes.push_back(2);//2是第一个素数
int square=2*2;//上限素数值的平方,如果待检验数大于它,那么要换一个更大的素数做上限
int upperlimit=0;//素数检验上限
for (int candidate = 3; candidate <= n; candidate++){
bool isPrime = true;
if (candidate>square){
upperlimit++;
square=primes[upperlimit] * primes[upperlimit];
}
for (int i=0; i<=upperlimit; i++){
if (candidate % primes[i] == 0){
isPrime = false;
break;
}
}
if(isPrime){
primes.push_back(candidate);
}
}
return primes;
}
//双向队列法,用迭代器遍历
deque<int> getPrimeDeque_iterator(int n){
deque<int> primes;
if (n<2) return primes;//不存在小于2的素数
primes.push_back(2);//2是第一个素数
int square=2*2;//上限素数值的平方,如果待检验数大于它,那么要换一个更大的素数做上限
deque<int>::iterator upperlimit=primes.begin();//素数检验上限
for (int candidate = 3; candidate <= n; candidate++){
bool isPrime = true;
if (candidate>square){
upperlimit++;
square=(*upperlimit) * (*upperlimit);
}
for (deque<int>::iterator i=primes.begin();i!=upperlimit;i++){
if (candidate % (*i) == 0){
isPrime = false;
break;
}
}
if(isPrime && (candidate % (*upperlimit) != 0)) primes.push_back(candidate);//由于循环检验遗漏了上限素数,故需要补上
}
return primes;
}