-
素數的定義
- 素數又稱爲質數,指在大於1的自然數中,除了1和它本身以外不再有其他因數的自然數
- 2是最小的質數
-
算法
- 暴力枚舉
- 開方 因數都是成對出現,
比如,100的因數有:1和100、2和50、4和25、5和20、10和10
即成對的因數,其中一個必然小於等於100的開平方,另一個大於等於100的開平方
因此只要判斷2~sqrt(n)的因數即可 - 埃拉託斯特尼(Eratosthenes)
- 創建長度爲 (n -2 + 1) 的數組, 初始值都爲 true
- 遍歷[2-n] , 每次將 i 的倍數的下標設置爲 false
- 倍數篩選 [j=2, i*j<=n, j++]
- 二次篩選 [j=i*i, j<=n, j+=i]
- 線性篩選
- 最後遍歷數組中爲 true 的元素就是素數
-
素數定理
素數的分佈是越往後越稀疏。或者說,素數的密度是越來越低。而素數定理,
說白了就是數學家找到了一些公式,用來估計某個範圍內的素數,大概有幾個。
在這些公式中,最簡潔的就是 x/ln(x).假設要估計1,000,000以內有多少質數,
用該公式算出是72,382個,而實際有78,498個,誤差約8個百分點。
該公式的特點是:估算的範圍越大,偏差率越小。
一般用x/ln x來估計某個範圍內素數的個數(誤差小於15%) -
實現
package prime
import (
"math"
)
// IsPrime judge whether the given number is prime
type IsPrime func(n uint64) bool
// IsPrimeByRangeEnum judge whether the given number is prime
func IsPrimeByRangeEnum(n uint64) bool {
if n < 2 {
return false
}
for i := uint64(2); i < n; i++ {
if n%i == 0 {
return false
}
}
return true
}
// IsPrimeBySqrtRangeEnum judge whether the given number is prime
func IsPrimeBySqrtRangeEnum(n uint64) bool {
if n < 2 {
return false
}
for i, l := uint64(2), uint64(math.Sqrt(float64(n))); i <= l; i++ {
if n%i == 0 {
return false
}
}
return true
}
// NPrime 獲取一個[2-n] 內的素數
func NPrime(n uint64, isPrime IsPrime) (ps []uint64) {
ps = make([]uint64, 0)
if n < 2 {
return
}
for i := uint64(2); i <= n; i++ {
if isPrime(i) {
ps = append(ps, i)
}
}
return
}
// NPrimeEratosthenes 埃拉託斯特尼, 最優 + 倍數篩選
func NPrimeEratosthenes(n uint64) (ps []uint64) {
ps = make([]uint64, 0)
if n < 2 {
return ps
}
N := make([]bool, n+1) // false: 素數, true: 不是素數
for i, l := uint64(2), uint64(math.Sqrt(float64(n))); i <= l; i++ {
if N[i] == false {
for j := uint64(2); i*j <= n; j++ {
N[i*j] = true // 倍數篩選: 同一元素會重複刪除
}
}
}
for i, l := uint64(2), n+1; i < l; i++ {
if N[i] == false {
ps = append(ps, i)
}
}
return ps
}
// NPrimeEratosthenes2 埃拉託斯特尼 + 二次篩選法
func NPrimeEratosthenes2(n uint64) (ps []uint64) {
ps = make([]uint64, 0)
if n < 2 {
return ps
}
N := make([]bool, n+1) // false: 素數, true: 不是素數
for i, l := uint64(2), uint64(math.Sqrt(float64(n))); i <= l; i++ {
if N[i] == false {
for j := i * i; j <= n; j += i {
N[j] = true // 二次篩選法: 假設每次i是素數,則下一個起點是 i*i ,把後面 [i*i, i*(i+1), i*(i+2), ..., n] 全部移除
}
}
}
for i, l := uint64(2), n+1; i < l; i++ {
if N[i] == false {
ps = append(ps, i)
}
}
return ps
}
// NPrimeEratosthenesLine 埃拉託斯特尼 + 線性篩選
func NPrimeEratosthenesLine(n uint64) (ps []uint64) {
ps = make([]uint64, 0)
if n < 2 {
return ps
}
N := make([]bool, n+1) // false: 素數, true: 不是素數
P := make([]uint64, n+1) // 保存素數
count := uint64(0) // 素數的個數
for i := uint64(2); i <= n; i++ {
if N[i] == false {
P[count] = i
count++
}
for j := uint64(0); j < count && P[j]*i <= n; j++ {
N[P[j]*i] = true
a := make([]uint64, n+1)
for i, v := range N {
if v {
a[i] = 1
}
}
if i%P[j] == 0 { // 保證每個合數只會被它的最小質因數篩去,因此每個數只會被標記一次,所以時間複雜度是O(n)
break
}
}
}
for i, l := uint64(2), n+1; i < l; i++ {
if N[i] == false {
ps = append(ps, i)
}
}
return ps
}
// 基準測試
var N = uint64(100000)
func BenchmarkNprimeIsPrime(b *testing.B) {
for i := 0; i < b.N; i++ {
NPrime(N, IsPrimeByRangeEnum)
}
}
func BenchmarkNprimeIsPrimeBySqrtRangeEnum(b *testing.B) {
for i := 0; i < b.N; i++ {
NPrime(N, IsPrimeBySqrtRangeEnum)
}
}
func BenchmarkNPrimeEratosthenes(b *testing.B) {
for i := 0; i < b.N; i++ {
NPrimeEratosthenes(N)
}
}
func BenchmarkNPrimeEratosthenes2(b *testing.B) {
for i := 0; i < b.N; i++ {
NPrimeEratosthenes2(N)
}
}
func BenchmarkNPrimeEratosthenesLine(b *testing.B) {
for i := 0; i < b.N; i++ {
NPrimeEratosthenesLine(N)
}
}
測試結果:
goos: darwin
goarch: amd64
pkg: github.com/feiquan123/algorithm/prime
// 暴力計算 n*n
BenchmarkNprimeIsPrime-8 1 3182596148 ns/op
// 暴力計算 n*sqrt(n)
BenchmarkNprimeIsPrimeBySqrtRangeEnum-8 55 20153547 ns/op
// 埃拉託斯特尼 - 倍數篩選
BenchmarkNPrimeEratosthenes-8 3261 334561 ns/op
// 埃拉託斯特尼 - 二次篩選
BenchmarkNPrimeEratosthenes2-8 3382 338191 ns/op
// 埃拉託斯特尼 - 線性篩選
BenchmarkNPrimeEratosthenesLine-8 1 23504143006 ns/op
PASS
ok github.com/feiquan123/algorithm/prime 30.136s