zoukankan      html  css  js  c++  java
  • 高效求解素数

    给出一个正整数,求出2-正整数之间的所有素数。所谓素数,就是除了1和它本身外不能被任何数整除的数。

    素数求解的问题是刚开始接触C语言就接触到的简单问题,也许你会写出下面的代码:

        int Prime_num(int end_num) // 求解从1-end_num间的所有素数
        {
            int result = 0;
            for(int i = 2; i < end_num; ++i){
                if(IsPrime(i)){
                    ++result;
                }
            }
            return result;
        }
        bool IsPrime(int num) // 判断num是否为素数
        {
            int i;
            for(i = 2; i < num; ++i){
                if(!(num % i)){
                    return false;
                }
            }
            return true;
        }

    该代码套用两层循环,从2遍历至end_num,对每一个数进行素数判断。时间复杂度O(n^2)。

    但是我们发现该算法在判断num是否为素数还有可优化的地方

    比如当num = 12 时判断num是否为素数:

    12 = 2 * 6

    12 = 3 * 4

    12 = sqrt(12) * sqrt(12)

    12 = 4 * 3

    12 = 6 * 2

    观察到以sqrt(12)为分界点,前后是相同的乘积式,这样我们只需要在IsPrime()函数中遍历到sqrt(num)就好了呢。

    好了,改进一下IsPrime()函数

        bool IsPrime(int num)
        {
            int i;
            for(i = 2; i <= sqrt(num); ++i){ //只需要遍历到 sqrt(num)就可以了哦
                if(!(num % i)){
                    return false;
                }
            }
            return true;
        }

    但是这还不是最高效的算法!

    在之前的素数求解过程中,我们从2开始遍历,2为素数,并且  2 * 2 = 4, 2 * 3 = 6, 2 * 4 = 8....都不会是素数了

    接下来的3也是素数,3 * 2 = 6, 3 * 3 = 9, 3 * 4 = 12....也都不是素数

    也就是说,只要我们在遍历过程中将该数的所有倍数排除掉,那么就会节省很大一部分的时间

    看下代码:

        int Prime_num(int end_num)
        {
            int result = 0;
            bool IsPrime[end_num];
            // 将所有元素初始化为true
            for(int i = 0; i < end_num; ++i){
                IsPrime[i] = true;
            }
            //遍历
            for(int i = 2; i < end_num; ++i){
                if(IsPrime[i]){
                    // 将 i 的倍数全部排除掉
                    for(int j = i * 2; j < end_num; j += i){
                        IsPrime[j] = false; // 非素数
                    }
                    ++result;
                    cout << i << endl;
                }
            }
            return result;
        }

    这里有一个细节需要说明,因为我们使用的是逆向思维。

    在数组IsPrime中,我们将每一个元素都初始化为true,即假设每一个元素都是素数。遍历每一个元素,并排除掉该元素的所有倍数(保留该元素本身),这样最终还是true的元素就是素数啦。

    而第一个素数是 2 ,所以我们从2 开始遍历。

    将 2 的所有倍数全部排除掉,用 IsPrime[i] = false来标记,4,6,8,10,.....,100(不能排除2本身哦,因为2是素数)

    接下来我们将 3 的所有倍数都排除掉,用IsPrime[i] = false来标记,6,9,12,....,99(同样3本身不能被排除)

    下一个数字是 4 ,这个时候我们需要对 4 的倍数进行排除吗?想清楚哦,由于 4 是 2 的倍数,所以 4 在 遍历到 2 的时候就已经被排除掉啦,所以现在IsPrime[4] == false,不执行排除操作(这里可能会产生疑问,不排除4的倍数,会不会有非素数数字被漏掉没有排除呢?不会啦,因为4是2的倍数,是4的倍数的数字也一定是2的倍数,在遍历2时就已经被排除光光啦)

    再下一个数字是 5 ,这个时候我们需要对 5 的倍数进行排除吗?答案是肯定的,因为5就是我们遇到的下一个素数(IsPrime[5] == true)。也就是说从除了1 和 5之外,5这个数不能被2,3,4整除(因为我们在遍历2,3,4的时候都没有把5排除掉),这不正好满足素数的定义吗?所以IsPrime[5] == true,5是我们要找的下一个素数i,将5的所有倍数全部排除掉。

    .......

    这样看来,是不是我们的算法就已经优化了很多啦

    但仔细看得话这里还有两个可以优化的点

     for(int i = 2; i < end_num; ++i){
                if(IsPrime[i]){
            ....

    还记得上面我们的sqrt(end_num)吗?由于因子具有对称性,因此这里我们也可以将这层循环缩减为sqrt(end_num)。
    还有一个地方
      for(int j = i * 2; j < end_num; j += i){
               IsPrime[j] = false; // 非素数
      }
    就是我们的排除过程
    在遍历到2时,我们要排除的是 2 * 2 = 4,... ,2 * 7 = 14..., 2 * 14 = 28,...,2 * 21 = 42....
    在遍历到3时,我们要排除的是 3 * 2 = 6, 3 * 3 = 9,...3 * 7 = 21 ....
    在遍历到5时,我们要排除的是 5 * 2 = 10,...,5 * 7 = 35...
    在遍历到7时,我们要排除的是 7 * 2 = 14, 7 * 3 = 21, 7 * 4 = 28, 7 * 5 = 35, 7 * 6 = 42, 7 * 7 = 49,....
    发现了吗?在遍历到7的时候,我们在 7 * 7之前的表达式都已经在之前的过程中被刷掉了呢。
    所以在该层循环我们从 j = i * i的位置开始就可以节省不少时间
    下面为最终优化后的代码
        int Prime_num(int end_num)
        {
            int result = 0;
            bool IsPrime[end_num];
            // 将所有元素初始化为true
            for(int i = 0; i < end_num; ++i){
                IsPrime[i] = true;
            }
            //遍历
            for(int i = 2; i*i < end_num; ++i){
                if(IsPrime[i]){
                    // 将 i 的倍数全部排除掉
                    for(int j = i * i; j < end_num; j += i){
                        IsPrime[j] = false; // 非素数
                    }
                }
            }
            for(int i = 2; i < end_num; ++i){
                if(IsPrime[i]){
                    ++result;
                }
            }
            return result;
        }

    该算法的时间复杂度比较难算,最终结果是O(N*loglogN)。

    参考资料:微信公众号labuladong

     
  • 相关阅读:
    maven项目添加Gson的依赖后无法启动,报错BeanCreationException:Error creating bean with name 'gsonBuilder'
    'bool' object is not callable
    UnicodeDecodeError: 'gbk' codec can't decode byte 0xa6 in position 9737: illegal multibyte sequence
    django.contrib.auth.middleware.AuthenticationMiddleware' must be in MIDDLEWARE in order to use the admin application.
    ForeignKey(unique=True) is usually better served by a OneToOneField.
    NameError: name 'reload' is not defined
    No module named 'django.core.urlresolvers'
    No module named 'ckeditor'
    [Docker] Leverage a Docker Maven plugin
    [Docker] Separate application image from database migration
  • 原文地址:https://www.cnblogs.com/GuoYuying/p/11674824.html
Copyright © 2011-2022 走看看