• 从关于素数的算法题来学习如何提高代码效率


    今天在博文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;
    }
  • 相关阅读:
    Identifier expected after this token
    需要整理的
    Context
    SharedPreferences
    一些常规注意事项
    一个点亮屏幕的service
    BroadcastReceiver中调用Service
    BroadcastReceiver
    Service
    微服务简介
  • 原文地址:https://www.cnblogs.com/studynote/p/3459704.html
Copyright © 2020-2023  润新知