问题来源与100以内的素数,求素数在100以内这个范围其实是很好求的,但是假如现在换成了1亿内的素数呢?有没有更快更好的算法?
我们先来看看常规解法:
func getPrimeNumber1() []int {
var i int
var sum = 0
var array = make([]int, 0)
for i = 1; i < maxNum; i++ {
var len = int(math.Sqrt(float64(i)))
var flag = 0
//i%2可以优化
if i > 2 && i%2 == 0 {
//sum++
continue
}
for j := 2; j <= len; j++ {
sum++
if i%j == 0 {
flag = 1
break
}
}
if flag == 0 {
array = append(array, i)
}
}
return array
}
常规解法中外循环从1~maxNum 遍历,内循环从 2 到 sqrt(i) 进行遍历,不停的 通过 i%j 取余来判定是否能被整除,同时可以将2的倍数剔除(通过步长剔除也可以),我们尝试将maxNum 扩大到1千万,此时算法执行了大概 18s左右,因此当计算大批量(范围内)的素数时并不太适合。
改进算法:
1. 我们用一个长度为 maxNum 的长度位的内存地址标示从 0-maxNum 中每个数是否是素数
2. 我们将 能被2整除的数进行标记(可以节约一半)
3. 外循环 循环 2 ~ sqrt(maxNum) 范围,内循环 循环 i ~ maxLen 步长为2,同时不停的计算 循环范围内的非素数,并标记出来。
4. 输出素数
//给定一个数字、类型长度、根号下的长度(位)算出 当前数 所属的 下标 以及校验位 leftVal
func getKeyVal(num int, typeLen int, typeLenSqrt int) (int, uint) {
//求商
var key = num >> uint(typeLenSqrt)
//var key = num / typeLen
//var val = num % typeLen
//求余
var valTmp = key << uint(typeLenSqrt)
var val = num - valTmp
var leftVal uint = 1 << uint(typeLen-val-1)
//fmt.Println(num, typeLen, val, val1)
//key 标示下标,leftVal 标示位移过后的校验位
return key, leftVal
}
//计算素数前对 一些基本的 偶数 以及 填充长度、数值进行计算
func getPrimeNumberPre() ([]uint, int) {
var iii iInt = 1
var byteW = 8
//var byteWStep = byteW / 2
var typeW = int(unsafe.Sizeof(iii))
var typeLen = int(typeW * 8)
var num = maxNum / typeLen
var tmp = make([]uint, num+1)
var i int
//对偶数进行处理的过程
var oneInt uint = 0
var commInt uint = 0
//170 的2进制正好能够覆盖64位下的所有偶数
for i = 0; i < typeW; i++ {
commInt += 170 << uint(i*byteW)
}
//最高位因为是uint 取0
oneInt = commInt >> 4
fmt.Printf("%b\n%b\n", oneInt, commInt)
//将每个数组下标对应的64个位置为所需
//可以尝试加速 前后同时修改
for i = 0; i <= num; i++ {
tmp[i] |= commInt
}
//注意下标为0开始的几位(1,2,3)
tmp[0] &= oneInt
//fmt.Printf("%b\n%b\n", tmp[0], tmp[1])
fmt.Printf("%b\n%b\n", oneInt, commInt)
return tmp, typeLen
}
//计算素数
func getPrimeNumber() []int {
var len = int(math.Sqrt(float64(maxNum)))
var typeLenSqrt = 6 //2的6此方等于 64
var i int
var array = []int{}
//var sum = 0
tmp, typeLen := getPrimeNumberPre()
for i = 2; i < len; i++ {
key, leftVal := getKeyVal(i, typeLen, typeLenSqrt)
if tmp[key]&leftVal == leftVal {
//sum++
continue
}
var maxLen = maxNum / i
var tmpKey = i * i
//如何在进行优化 步长为 2 说明 没有必要再进行 乘法操作
for j := i; j <= maxLen; j += 2 {
newKey, newLeft := getKeyVal(tmpKey, typeLen, typeLenSqrt)
tmp[newKey] |= newLeft
tmpKey += i + i
//sum++
}
}
//maxGroup := maxNum / typeLen
for i = 1; i < maxNum; i++ {
key, leftVal := getKeyVal(i, typeLen, typeLenSqrt)
if tmp[key]&leftVal != leftVal {
array = append(array, i)
//fmt.Println("----", i)
}
}
//fmt.Println("===", sum)
return array
}
经过优化后,计算1千万以内的素数基本上是 66ms,1亿以内的素数800ms左右(mac pre i5 8g 语言版本 go1.10 ),不同环境可能结果不同,此处执行数据仅作为参考,但是已经相对能够在1s之内算出1亿范围内的素数了,效果还是很明显。第二种算法等于变相利用位操作标记了非素数,在后续的标记过程中直接跳过。