哥德巴赫猜想9位数以内的验证——优化版、二次优化版、终极优化plus版!

哥德巴赫猜想于1742年提出,是数论中存在已久的未解问题之一。用现代的数学语言,哥德巴赫猜想可以陈述为:任一大于2的偶数,都可以表示成两个素数之和。

//不禁想说一句,古人属实优秀!

本文将进行哥德巴赫猜想9位数以内的验证,内容包括对问题的理解,寻找问题的解决办法,初步拟出算法,算法优化,二次优化,换个角度再优化,使用位运算终极优化。

博文稍长,需要耐心读;笔者水平有限,有错误还望指正。


一、切入问题

  1. 对于任意一个大于2的偶数,把它分解为两个质数之和,代码角度讲就是定义一个num,同时满足 i 是质数,(num-i)是质数,这样便找到了一个分解方式。

  2. 判断 i 和(num-i)是否为质数,可以通过编写一个函数实现。

  3. 验证范围maxNum由用户输入,我们从2开始遍历到maxNum的所有偶数。

  4. 特别提醒
    (1)为了测试代码效率,我们引入了标准库中的time.h,用来计算程序运行时间。

#include <time.h>

(2)为了表示boolean类型,我们定义了

typedef unsigned char       boolean;

写进mec.h,然后把头文件和.c源文件放到了一个文件夹下。

二、代码1.0版

#include <stdio.h>
#include <time.h>
#include "mec.h"

boolean isPrime(int num);
boolean isGuessRight(int num);
boolean Goldbach(int maxNum);

boolean Goldbach(int maxNum) {    
    int i;
    for (i = 6; i < maxNum; i += 2) {        
        if (!isGuessRight(i)) {            
            printf("不符合哥德巴赫猜想的数:%d\n", i);            		
            return FALSE;        
        }    
    }
    return TRUE;
}

boolean isGuessRight(int num) {    
    int i;
    for (i = 3; i <= num / 2; i += 2) {        
        if (isPrime(i) && isPrime(num - i)) {
            return TRUE;        
        }    
    }
    return FALSE;
}

//判断质数的函数
boolean isPrime(int num) {    
    int i;
    for (i = 2; i < num && num % i; i++) {
    }
    return i >= num;
}

int main(int argc, char const *argv[]){    
    int maxNum;    
    boolean ok;    
    long startTime;    
    long endTime;
    
    printf("请输入范围:");    
    scanf("%d", &maxNum);
    startTime = clock();    
    ok = Goldbach(maxNum);    
    endTime = clock();    
    endTime -= startTime;
    
    printf("耗时:%d.%03d秒\n", endTime / 1000, endTime % 1000);
    if (!ok) {        
        printf("Oh my god! Goldbach's guess is wrong!\n");    
    } else {        
        printf("Goldbach's guess is right!\n");    
    }
    
    return 0;
}

三、耗时测试

本来我们要进行9位数以内的哥德巴赫猜想验证,但是……当我进行6位以内的数验证时……就已经等了好久:
在这里插入图片描述
(鬼知道我在这六百秒的时间里经历了什么……啊无非就是以为我的程序出错了,想关掉终端又关不了,就瞎点,然后还尝试中断程序运行,我不知道怎么中断,就敲cls,其实没用的,最后佛了,等着它。)

所以9位数的……我不打算等了,外面花儿要谢辽。

其实我们只是为了说明问题,和后续的程序运行效率做对照,所以不在此浪费时间。

四、思路优化

分析代码1.0版,哥德巴赫猜想局部验证的程序最耗时且易做优化的部分应该是: 质数的判断

为什么这么说呢?

其一,假设num是一个很大的质数,比如197,按照从3开始,每一个的奇数验证能否整除197,需要验证97次左右。然而事实上,根本没必要从3验证到195,只需要验证到197的算数平方根就可以了。

其二,对于每一个大于2的偶数,它一定不是质数,直接返回FALSE就可以,根本不用进入for循环判断,而for循环又是极浪费时间的。

所以我们的主要任务是优化质数判断函数。

五、代码2.0版

局部代码:

//优化后的isPrime()函数
boolean isPrime(int num) {
    int i;
    int range = ((int) sqrt(num)) + 1;
    
    if (num == 2) {
        return TRUE;    
    }    
    if (num % 2 == 0) {        
        return FALSE;    
    }    
    for (i = 3; i < range && num % i; i += 2) {
    }
    
    return i >= range;
}

六、耗时测试

先输6位以内的和上面做对照:
在这里插入图片描述
之前耗时622秒,现在耗时0.7秒,622 / 0.7 = 876,提升了八百多倍,真棒!

再来看看7位数的:
在这里插入图片描述
22秒还可以

8位数以内的我还在等……

继续等……

来了:
在这里插入图片描述
500多秒又有点长

但总的来说,2.0版本的改进还是有显著成效的!

不过,还能再优化吗?of course!

七、创新优化

不妨换个思维:对于质数的判断,如果能从推导是否为质数,改为直接判断是否为质数,是不是就快多了?

所谓直接判断,现有一个这样的思路:创建一个数组,数组元素的值为0或1。0表示元素所对应的数组下标是质数,1表示元素所对应的数组下标不是质数。

基于思想:以空间换取时间

如果采用上方式完成质数判断,则需要以下步骤:
1.先“标识”出所有的质数。
2.判断一个数是否为质数。

那么“标识”出所有的质数具体该怎么做呢?

1.先建一个质数池 primePool[ ],或者写成 *primrPool,再动态分派存储空间。
2.筛选质数,主要步骤:
(1).筛除2的倍数。
(2).筛除3的倍数。
(3).筛除5的倍数。(4的倍数也是2的倍数,已被筛除)
(4).筛除7的倍数。(6的倍数也是2,3的倍数,已被筛除)
(5).筛除11的倍数。(8,9,10的倍数也是2,3,2的倍数,已被筛除)
…………
其实,对于i,没必要处理2i、3i、(i-1)*i的数值,只需要直接从i^2的元素开始筛除。

基于此我们引入了“质数池”的概念,用来存放0或1,请看下列代码。

八、代码3.0版

#include <stdio.h>
#include <time.h>
#include <malloc.h>
#include "mec.h"

boolean *primePool;
boolean isPrime(int num);
boolean isGuessRight(int num);
boolean Goldbach(int maxNum);
void makePrime(int maxNum);

void makePrime(int maxNum) {
    int i;    
    int j;
    
    primePool = (boolean *) calloc(sizeof(boolean), maxNum);
    for (i = 4; i < maxNum; i += 2) {
        primePool[i] = 1;    
    }
    
    for (i = 3; i*i < maxNum; i += 2) {
        if (primePool[i] == 0) {       
             for (j = i * i; j < maxNum; j += i) {                  
             primePool[j] = 1;            
             }        
         }    
     }
}

boolean Goldbach(int maxNum) {    
    int i;  
    
    for (i = 6; i < maxNum; i += 2) {        
        if (!isGuessRight(i)) {            
            printf("不符合哥德巴赫猜想的数:%d\n", i);               
            return FALSE;        
        }    
    }
    return TRUE;
}

boolean isGuessRight(int num) {    
    int i;
    
    for (i = 3; i <= num / 2; i += 2) {        
        if (isPrime(i) && isPrime(num - i)) {                       
            return TRUE;        
        }
    }
    return FALSE;
}

boolean isPrime(int num) {    
    return primePool[num] == 0;
}

int main(int argc, char const *argv[])
{    
    int maxNum;    
    boolean ok;    
    long startTime;    
    long midTime;    
    long endTime;
    
    printf("请输入范围:");    
    scanf("%d", &maxNum);
    startTime = clock();    
    makePrime(maxNum);    
    midTime = clock() - startTime;    
    ok = Goldbach(maxNum);     
    endTime = clock() - startTime;
    printf("筛选质数耗时:%d.%03d秒\n哥德巴赫猜想验证耗时:%d.%03d秒\n",midTime / 1000, midTime % 1000,        endTime / 1000, endTime % 1000);
    
    if (!ok) {        
        printf("Oh my god! Goldbach's guess is wrong!\n");    
    } else {        
    printf("Goldbach's guess is right!\n");    
    }
    
    free(primePool);        
    return 0;
}

九、耗时测试

同样,先看6位数以内的:
在这里插入图片描述
8位数以内的:
在这里插入图片描述
是不是也很棒!

9位数的验证终于也出世啦:
在这里插入图片描述

十、评价3.0版

总体来说,3.0版本的操作是成功的,且是极快速的,但是存在大量内存消耗。因为,上面的方式是用一个unsigned char (即boolean)来表示0或者1的,这种方式使得内存利用率非常低,一个字节只用了1/8也太浪费辽!

所以,有了plus无敌版的问世。

十一、终极优化

之前写过位运算的巧妙使用,不妨先来看看这篇文章:位运算的“凶悍”操作
这里就不一 一再介绍了,后面的代码会直接自带位运算的buff加成。

思路:考虑到用一个位表示一个数,而一个字节有8个位,所以每一个字节从0~7标注。
在这里插入图片描述
申请字节数:maxNum / 8 + 1

明晰两个重要转换:

x = num / 8; <=> x = num >> 3;
y = num % 8; <=> y = num & 7;

十二、超级凶悍无敌plus版

在头文件mec.h中注入:

#define SET(n, i)   ((n) | (1 << ((i) ^ 7)))
#define CLR(n, i)   ((n) & ~(1 << ((i) ^ 7)))
#define GET(n, i)   (((n) >> ((i) ^ 7)) & 1)

对makePrime函数做的局部改进:

//plus版-质数池
void makePrime(int maxNum) {    
    int i;    
    int j;
    
    primePool = (boolean *) calloc(sizeof(boolean),         (maxNum >> 3) + 1);
    
    for (i = 4; i < maxNum; i += 2) {        
        primePool[i >> 3] = SET(primePool[i >> 3], i & 7);    
    }
    
    for (i = 3; i*i < maxNum; i += 2) {        
        if (GET(primePool[i >> 3], i & 7) == 0) {            
            for (j = i * i; j < maxNum; j += i) {                
                primePool[j >> 3] = SET(primePool[j >> 3], j & 7);            
            }        
        }    
    }
}

十三、耗时测试

老规矩,先看6位数以内的:
在这里插入图片描述
与1.0版做个对比吧:622 / 0.3 = 2073
提速两千倍,凶不凶悍!!!

那我们最初的执念——9位数以内验证呢,来看:
在这里插入图片描述
时间基本与3.0版持平,但是空间利用率却比3.0版高出不少,所以还是很值得采纳的。

十四、感悟

1.写算法要兼顾时间复杂度、空间复杂度。
2.算法优化的本质是做深入的数学分析。
3.写的代码要“会说话”。
4.打开思维,大胆创新,追求卓越。
5.处理好细节。


完结,撒花!
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值