哥德巴赫猜想:任一大于2的偶数num都可分解成两个质数之和;
num可以直接从6开始,并且因为num是偶数,由于大于2的偶数都是非质数,所以验证i和num-i都是质数的i可以直接从大于3的奇数开始,验证n以内的所有偶数是否满足哥德巴赫猜想,大体的伪代码如下:
for(num = 6; num <= maxNum && TRUE == isGoldbachRight; num += 2) {
for(canDivided = FALSE, i = 3; i <= num/2 && FALSE == canDivided; i += 2) {
if(isPrime(maxNum, numPool, i) && isPrime(maxNum, numPool, num - i)) {
canDivided = TRUE;
break;
}
}
if(FALSE == canDivided) {
isGoldbachRight = FALSE;
break;
}
}
由此可见,整个功能由外层循环控制和内部判断是否为质数的函数嵌套完成。
我们用常规的方法判断一个数是否是素数,时间复杂度为O(), 代码如下:
boolean isPrime(int num) {
int i;
int sqrtNum;
if(num == 2) {
return TRUE;
}
if(num & 1 == 0) {
return FALSE;
}
sqrtNum = (int) sqrt(num) + 1;
for(i = 3; i <= sqrtNum && num % i; i += 2) {
;
}
return i > sqrtNum;
}
由此,总的时间复杂度为:O(),完成所有的7位数内的哥德巴赫猜想,输入为1000 0000 需要时间:
所以可以看出基于这种质数判断方法的验证十分耗时。想要加快速度就必须要从验证一个数是否为质数开始着手优化;
质数筛选法:判断n内的哪些数字为素数的具体做法是:先把n个自然数按次序排列起来。1不是质数,也不是合数,要划去。第二个数2是质数留下来,而把2后面所有能被2整除的数都划去。2后面第一个没划去的数是3,把3留下,再把3后面所有能被3整除的数都划去。3后面第一个没划去的数是5,把5留下,再把5后面所有能被5整除的数都划去。这样一直做到能把n+1整除的数全部划去,就会把不超过n的全部合数都筛掉,留下的就是超过n的数就全部都是质数。
通过这种思想,我们可以用二进制位的下标来表示这个数值,该二进制位本身的值来反映它这个下标是否为素数。所以就需要先对n位二进制位进行初始化,先将其全部置为0,再使用筛选法把非质数的位全部置1,这就需要借助位运算来完成。如果需要验证n以内的所有数,就需要n个二进制位来记录质数和非质数,对应的字节数是:(n+7)/8 <=>(n+ 7)>> 3。因为n比较大,就用collac来申请堆空间。
不难得出下标为num的位,应该位于第: num/8 <=> num >> 3个字节,对应的index为:num%8 <=> num & 7; 要完成对质数的筛选,和判断某个数是否为质数需要先掌握三个基于位运算的操作: 分别是将一个字节的从左至右第index位置1和置0,和读取第index位的值(0或1)。 数值1在内存中一个字节存储的形式应该为 :0000 0001;
value为1字节unsign char: _ _ _ _ _ _ _ _
index: 0 1 2 3 4 5 6 7
将1左移7-index位:1 << 7 – (index) <=> 1 << ((index) ^ 7);
SETB(value, index) ((value) |= (1 << ((index) ^ 7))):将value的第index位置为1;
CLRB(value, index) ((value) &= (~(1 << ((index) ^ 7)))):将value的第index位置为0;
GETB(value, index) (!!((value) & (1 << ((index) ^ 7)))):读取第index位的值,结果为0或1;
完成筛选的代码如下:
void choosePrime(int maxNum, uc *numPool, int numPoolSize) {
int i;
int pos;
int sqrtNum = (int) (sqrt(maxNum) + 1);
for(i = 4; i <= maxNum; i += 2) {
SETB(numPool[i >> 3], i & 7);//将所有的偶数位位置1
}
for(i = 3; i <= sqrtNum; i += 2) {//再对奇数位进行筛选
if(GETB(numPool[i >> 3], i & 7) == 0) {//先判断该位是否质数再决定是否对其倍数置1.
for(pos = i * i; pos <= maxNum; pos += i) {
SETB(numPool[pos >> 3], pos & 7);
}
}
}
}
筛选完成之后,在需要知道某个数是否为质数时,对其对应的二进制位进行读取就能以O(1)的时间知道结果。
全部代码如下:
其实我们发现由大于等于6的偶数分解成的两个数,因为任意一个都大于2,并且在proofGoldbach(int maxNum, uc *numPool)函数中,i的取值只有奇数,所以在筛选的过程中可以不考虑偶数位。
mecBase.h头文件内容如下
#ifndef _MEC_BASE_H_
#define _MEC_BASE_H_
typedef unsigned char boolean;
typedef boolean uc;
#define TRUE 1
#define FALSE 0
#define SETB(value, index) ((value) |= (1 << ((index) ^ 7)))
#define CLRB(value, index) ((value) &= (~(1 << ((index) ^ 7))))
#define GETB(value, index) (!!((value) & (1 << ((index) ^ 7))))
#endif
全部代码如下:
#include <stdio.h>
#include <time.h>
#include <math.h>
#include <malloc.h>
#include "include\\mecBase.h"
boolean isPrime(int maxNum, uc *numPool, int num);
void proofGoldbach(int maxNum, uc *numPool);
void choosePrime(int maxNum, uc *numPool, int numPoolSize);
void choosePrime(int maxNum, uc *numPool, int numPoolSize) {
int i;
int pos;
int sqrtNum = (int) (sqrt(maxNum) + 1);
for(i = 4; i <= maxNum; i += 2) {
SETB(numPool[i >> 3], i & 7);
}
//由于i为奇数,num为偶数,所以i和num-i均为奇数,
//对于偶数位其实可以直接跳过,只需要筛选奇数位即可
for(i = 3; i <= sqrtNum; i += 2) {
if(GETB(numPool[i >> 3], i & 7) == 0) {
for(pos = i * i; pos <= maxNum; pos += i) {
SETB(numPool[pos >> 3], pos & 7);
}
}
}
}
void proofGoldbach(int maxNum, uc *numPool) {
int num;//要验证的数
int i;
boolean isGoldbachRight = TRUE;
boolean canDivided;
for(num = 6; num <= maxNum && TRUE == isGoldbachRight; num += 2) {
for(canDivided = FALSE, i = 3; i <= num/2 && FALSE == canDivided; i += 2) {
//如果遇到无法分解的数,则证明哥德巴赫猜想是错误的
if(isPrime(maxNum, numPool, i) && isPrime(maxNum, numPool, num - i)) {
//由于i为奇数,num为偶数,所以i和num-i均为奇数,对于偶数位可以直接跳过
// printf("%d = %d + %d\n", num, i, num - i);
canDivided = TRUE;
break;
}
}
if(FALSE == canDivided) {
isGoldbachRight = FALSE;
break;
}
}
if(FALSE == isGoldbachRight) {
printf("发现了不能分解的数!:%d不能分解!\n", num);
} else {
printf("哥德巴赫猜想验证:%d以内的数均正确!\n", maxNum);
}
}
boolean isPrime(int maxNum, uc *numPool, int num) {
if(num > maxNum) {
return FALSE;
}
return GETB(numPool[num >> 3], num & 7) == 0;
}
int main() {
int maxNum;
clock_t startTime;
clock_t chooseTime;
clock_t endTime;
uc *numPool;
int numPoolSize;
printf("请输入最大数:");
scanf("%d", &maxNum);
startTime = clock();
numPoolSize = (maxNum + 7) >> 3;
numPool = (uc *) calloc(sizeof(uc), numPoolSize);
choosePrime(maxNum, numPool, numPoolSize);
chooseTime = clock();
proofGoldbach(maxNum, numPool);
free(numPool);
endTime = clock();
endTime -= startTime;
chooseTime -= startTime;
printf("筛选质数耗时:%d.%d秒\n", chooseTime / 1000, chooseTime % 1000);
printf("共耗时:%d.%d秒\n", endTime / 1000, endTime % 1000);
return 0;
}
完成8位数以内所有数的验证耗时为:
通过这个小例子,我们可以看出位运算的威力,能够巧妙的使用位运算将大大的提高速度,减少运算时间。
第一次把自己的学习笔记写成博客,有不足的地方非常感谢大家批评指正!一定虚心接受。