通过求素数算法管窥STL中vector、list、deque的性能

问题需求是求整数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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值