歌德巴赫猜想8位数内的验证

哥德巴赫猜想:任一大于2的偶数num都可分解成两个质数之和; 

        num可以直接从6开始,并且因为num是偶数,由于大于2的偶数都是非质数,所以验证inum-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位数以内所有数的验证耗时为:

通过这个小例子,我们可以看出位运算的威力,能够巧妙的使用位运算将大大的提高速度,减少运算时间。

第一次把自己的学习笔记写成博客,有不足的地方非常感谢大家批评指正!一定虚心接受。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值