zoukankan      html  css  js  c++  java
  • 从关于素数的算法题来学习如何提高代码效率

    今天在博文C语言初学者代码中的常见错误与瑕疵(5)看了一个关于素数的算法题,如下:

    素数

    在世博园某信息通信馆中,游客可利用手机等终端参与互动小游戏,与虚拟人物Kr. Kong 进行猜数比赛。

    当屏幕出现一个整数X时,若你能比Kr. Kong更快的发出最接近它的素数答案,你将会获得一个意想不到的礼物。

    例如:当屏幕出现22时,你的回答应是23;当屏幕出现8时,你的回答应是7;

    若X本身是素数,则回答X;若最接近X的素数有两个时,则回答大于它的素数。

    输入:第一行:N 要竞猜的整数个数

    接下来有N行,每行有一个正整数X

    输出:输出有N行,每行是对应X的最接近它的素数

    样例:输入

    4

    22

    5

    18

    8

    输出

    23

    5

    19

    7

    看到这个算法题我们首先要做的就是实现一个函数,来求出一个数是否是质数。下面我们来简单的实现一下:

    bool isPrime(int num)
    {
        if(num < 2) return false;
        for(int i=2; i*i<num; ++i){
            if(num % i == 0) return false;
        }
        return true;
    }


    由于这个函数在算法中会多次用到,我们用下面的测试来查看这个基本函数的效率

    void test(){
        clock_t start = clock();
        for(int i=1; i <= 100000; ++i){
            isPrime(1000000007);
        }
        clock_t end = clock();
        cout << endl << static_cast<double>(end - start)/CLOCKS_PER_SEC << endl;
    }

    运行,得到结果12.158

    因为期间我们可能会进行重复的计算,对这个问题我一开始想到的解决方法就是建立一个质数表,我们可以直接通过查找表来快速的确定一个数是否是质数。当要判断的数很大时,需要占用很大的空间来建表,为了节约空间,我将每一位都充分用上了。

    #define GETNUM(x) psum[((x)>>3)]&(1<<(((x)&7)-1))
    #define SETNUM(x) psum[((x)>>3)] &= (~(1<<(((x)&7)-1)))
    bool isPrime(int num)
    {
        if(num < 2) return false;
        int size = (num>>3)+1;
        unsigned char *psum = new unsigned char[size];
        memset(psum, 0xFF, size);
        for(int i=2; i*i<num; ++i){
            if(GETNUM(i)){
                for(int j=i<<1; j<=num; j+=i){
                    SETNUM(j);
                }
            }
        }
        bool result = GETNUM(num);
        delete [] psum;
        return result;
    }


    因为质数表建立起来以后,之后的判断直接取值就行了,所以我们就不做循环了,直接看它运行一次的时间,竟然用了29.853!耗时太长了,建这个表的时间可以进行20万次试除法判断了。

    在经过一定的分析后,我将这个过程进行了一下优化

    #define GETNUM(x) psum[((x)>>3)]&(1<<(((x)&7)-1))
    #define SETNUM(x) psum[((x)>>3)] &= (~(1<<(((x)&7)-1)))
    bool isPrime2(int num)
    {
        if(num < 2) return false;
        int size = (num>>3)+1;
        unsigned char *psum = new unsigned char[size];
        memset(psum, 0xFF, size);
        for(int j=4; j<num; j+=2){
            SETNUM(j);
        }
        for(int i=3; i*i<num; ++i){
            if(GETNUM(i)){
                int step = i<<1;
                for(int j=i*i; j<=num; j+=step){
                    SETNUM(j);
                }
            }
        }
    
        bool result = GETNUM(num);
        delete [] psum;
        return result;
    }

    上面的优化,我先是直接将2的倍数都淘汰掉,接着,基于在进行i的倍数判断时,所有i的i-1以下的倍数都已经被淘汰掉了这一点,直接从i的平方开始淘汰,而且基于偶数倍能被2整除这一点,将步长调整为i*2.

    经过优化,时间缩短为16.429,可是这个结果明显是不能让人满意滴。。。

    这时我参看了一下博文一个超复杂的间接递归——C语言初学者代码中的常见错误与瑕疵(6),发现只是计算部分质数表,再利用质数表来加快质数的试除法这个方案很有可行性,于是赶紧行动。先进行预算,再进行试除法判断质数。

    void precalc(int size, int * primes, int &pnum)
    {
        bool *psum = new bool[size+1];
        for(int j=4; j<=size; j+=2){
            psum[j] = false;
        }
        memset(primes, 0, size * sizeof(int));
        primes[0] = 2;
        pnum = 1;
        int i=3;
        for(; i*i<=size; ++i){
            if(psum[i]){
                primes[pnum] = i;
                ++pnum;
                int step = i<<1;
                for(int j=i*i; j<=size; j+=step){
                    psum[j] = false;
                }
            }
        }
        for(;i<=size; ++i){
            if(psum[i]){
                primes[pnum] = i;
                ++pnum;
            }
        }
        delete [] psum;
    }
    bool isPrime(int num, const int * primes, int pnum)
    {
        for(int i=0; i<pnum; ++i){
            if(num % primes[i] == 0) return false;
        }
        return true;
    }
    
    void test(){
        clock_t start = clock();
        const int num = 1000000007;
        int size = static_cast<int>(sqrt(static_cast<double>(num)));
        int *primes = new int[size];
        int pnum;
        precalc(size, primes, pnum);
        for(int i=1; i <= 100000; ++i){        
            isPrime(num, primes, pnum);
        }
        delete [] primes;
        clock_t end = clock();
        cout << endl << static_cast<double>(end - start)/CLOCKS_PER_SEC << endl;
    }
    View Code


    改进的结果是令人振奋滴,时间缩短为0.021.

    解决了素数判断问题,得到想要的算法就很容易了

    void precalc(int size, int * primes, int &pnum)
    {
        bool *psum = new bool[size+1];
        for(int j=4; j<=size; j+=2){
            psum[j] = false;
        }
        memset(primes, 0, size * sizeof(int));
        primes[0] = 2;
        pnum = 1;
        int i=3;
        for(; i*i<=size; ++i){
            if(psum[i]){
                primes[pnum] = i;
                ++pnum;
                int step = i<<1;
                for(int j=i*i; j<=size; j+=step){
                    psum[j] = false;
                }
            }
        }
        for(;i<=size; ++i){
            if(psum[i]){
                primes[pnum] = i;
                ++pnum;
            }
        }
        delete [] psum;
    }
    bool isPrime5(int num, const int * primes, int pnum)
    {
        for(int i=0; i<pnum && primes[i]*primes[i] < num; ++i){
            if(num % primes[i] == 0) return false;
        }
        return true;
    }
    int get_nearest(int num, const int * primes, int pnum)
    {
        if(isPrime5(num, primes, pnum)) return num;
        int len;
        if(num % 2 == 0){
            len = 1;
        }
        else{
            len = 2;
        }
        while(true){
            if(isPrime5(num+len, primes, pnum)) return num + len;
            if(isPrime5(num-len, primes, pnum)) return num - len;
            len += 2;
        }
    }
      
    int _tmain(int argc, _TCHAR* argv[])
    {
        cout<<"请输入数据"<<endl;
        int count;
        cin>>count;
        int *data = new int[count];
        //最大的一个数
        int maxnum = 0;
        for(int i=0; i<count; i++){
            cin>>data[i];
            if(maxnum < data[i]){
                maxnum = data[i];
            }
        }
        int size = static_cast<int>(sqrt(static_cast<double>(maxnum)));
        int *primes = new int[size];
        int pnum;
        precalc(size, primes, pnum);
        for(int i=0; i<count; i++){
            cout<<get_nearest(data[i], primes, pnum)<<endl;
        }
        delete [] primes;
        delete [] data;
        cin.get();
        return 0;
    }
  • 相关阅读:
    关闭编辑easyui datagrid table
    sql 保留两位小数+四舍五入
    easyui DataGrid 工具类之 util js
    easyui DataGrid 工具类之 后台生成列
    easyui DataGrid 工具类之 WorkbookUtil class
    easyui DataGrid 工具类之 TableUtil class
    easyui DataGrid 工具类之 Utils class
    easyui DataGrid 工具类之 列属性class
    oracle 卸载
    “云时代架构”经典文章阅读感想七
  • 原文地址:https://www.cnblogs.com/studynote/p/3459704.html
Copyright © 2011-2022 走看看