素数筛法(golang实现)

248 阅读2分钟

求小于n的素数,除了用暴力筛选以外,还可以使用素数筛法,效率更高。

Eratosthenes 筛法

先假设2-n中所有的数都是素数。从2开始,依次将222*2232*3...设为合数,之后依次筛选3,5,7...等素数的倍数,直到超过n。以n=20为例子,先假设所有的数都是合数,再将素数的倍数转为素数:

第一次选取2的倍数,筛选结果:4,6,8,10,12,14,16,18

第二次选取3的倍数,筛选结果:9,12,15,18

第三次选取5的倍数,筛选结果:10,15

第四次选取7的倍数,筛选结果:14

...

以此类推,最后筛选结果的合集为:4,6,8,9,10,12,14,15,16,18

即素数为剩下的2,3,5,7,11,13,17,19

代码实现如下:

func eratosthenes(n int) []int {  
    res := make([]int, 0)  
    //isPrime是否是素数,0-是,1-否  
    isPrime := make([]int, n)  
  
    for i := 2; i < n; i++ {  
        if isPrime[i] == 0 {  
            res = append(res, i)  
            for j := 2; j <= n; j++ {  
                if i*j >= n {  
                    break  
                }  
                isPrime[i*j] = 1  
            }  
        }  
    }  
    return res  
}

时间复杂度为O(nloglogn)O(nloglogn)

不足之处:同一个数,可能会被多次筛选,例如,第一次筛选的时候,筛选了6,第二次筛选的时候,也筛选了6。可以用euler筛法处理这个问题。

Elur筛法

增加一个素数数组,用来保存已经确认了的素数。

第一次筛选(各个素数的2倍):4,此时3还没有进入素数数组(即还没有确认为素数)

第二次筛选(各个素数的3倍):6,9

第三次筛选(各个素数的4倍):8,因为4 % 2 == 0, break,因为4是2的倍数,那么4*3=12也一定是2的倍数,在接下来的筛选中,还要计算2的其他倍数,一定能覆盖这个数,因此可以break。

第四次筛选(各个素数的5倍):10,15

第五次筛选(各个素数的6倍):12,因为6 % 2 == 0,break,进行下一次筛选

第六次筛选(各个素数的7倍):14

第七次筛选(各个素数的8倍):16,因为8 % 2 == 0,break,进行下一次筛选

依此类推...

代码如下:

func euler(n int) []int {  
    count := 0  
    primes := make([]int, 0)  
    //isPrime是否是素数,0-是,1-否  
    isPrime := make([]int, n)  
  
    for i := 2; i < n; i++ {  
        if isPrime[i] == 0 {  
            primes = append(primes, i)  
            count += 1  
        }  
        for j := 0; j < count; j++ {  
            if i*primes[j] >= n {  
                break  
            }  
            isPrime[i*primes[j]] = 1  
            //保证每个数只筛选一次  
            if i%primes[j] == 0 {  
                break  
            }  
        }  
    }  
    return primes  
}

对于18-20行if i%primes[j] == 0 { break }的解释为:如果i是primes[j]的倍数,那么i的倍数一定也是primes[j]的倍数,那么在接下来的筛选中,一定可以找到这个primes[j]的倍数,参考上文第三次筛选的结果。