与 "optimized" 迭代素数搜索相比,为什么埃拉托色尼筛法这么慢? (Python 3)

Why is Sieve of Eratosthenes so slow compared to "optimized" iterated prime search? (Python 3)

我尝试了一些不同版本的 Sieve,包括我自己的和来自不同页面的。不过,它们都有一个共同点。它们比我正在使用的迭代方法慢,这与我读过的关于 it.Is 的所有内容相悖,或者这是因为 python 列表处理速度慢?

from time import time
from random import randint

def isPrime(n):
    if (n <= 3):
        return (n >= 2)
    i = 5
    if (n % 2 == 0 or n % 3 == 0) :
        return False
    for i in range(5, int(n**.5)+1, 6):
        if (n%i == 0 or n%(i+2) == 0):
            return False
    return True

def sieveOfEratosthenes(n):
    array = [True for i in range((n-1)//2)]
    i = 3
    for e in array:
        if e is True:
            for k in range(i**2, n+1, 2*i):
                array[k//2-1] = False
        i += 2
    return [2]+[2*i+1 for i, e in enumerate(array, 1) if e is True]

def runIsPrime(arr):
    start = time()
    for e in arr:
        isPrime(e)
    print("Primes:\t\t", time()-start)

def runSieveOfEratosthenes(arr):
    start = time()
    primes = sieveOfEratosthenes(max(arr))
    #print("Sie. mid.:\t", time() - start)
    for e in arr:
        e in primes
    print("Sieve tot.:\t", time()-start)

upto = 10**6
times = 10**3
arr = [randint(2, upto) for i in range(times)]

runIsPrime(arr)
runSieveOfEratosthenes(arr)

给出打印输出:

Primes:      0.0019884109497070312 
Sieve tot.:  1.1940925121307373

您的 sieveOfEratosthenes 函数 returns 一个列表,并检查一个值是否在列表中 O(n)。但是,即使在将代码更改为使用 set 之后,筛子也会比您的直接方法慢:

def runSieveOfEratosthenes(arr):
    start = time()
    primes = set(sieveOfEratosthenes(max(arr)))
    #print("Sie. mid.:\t", time() - start)
    for e in arr:
        e in primes
    print("Sieve tot.:\t", time()-start)

upto = 10**6
times = 10**3
arr = [randint(2, upto) for i in range(times)]

runIsPrime(arr)
runSieveOfEratosthenes(arr)

输出:

Primes:      0.0013408660888671875
Sieve tot.:  0.19181394577026367

这是因为您的函数实际上在复杂性上有所不同。 isPrime 检查到根并提早退出,这使其可以快速处理小的复合数。 sieveOfEratosthenes 构建一个筛子,它是 O(n log log n) 并且它不依赖于列表的长度。让我们看看我们是否只传递更大尺寸的列表:

In [57]: ps = [996361] * 10**4

In [58]: runIsPrime(ps)
Primes:      0.20616984367370605

In [59]: runSieveOfEratosthenes(ps)
Sieve tot.:  0.1783740520477295

您可以看到,runIsPrime 性能急剧下降,而 runSieveOfEratosthenes 并没有太大变化。如果您认为这里可能涉及缓存,让我们准备不同素数的列表:

In [64]: ps2 = [x for x in range(10**5, 10**6) if isPrime(x)]

In [65]: len(ps2)
Out[65]: 68906

In [66]: ps2[-1]
Out[66]: 999983

In [67]: runIsPrime(ps2)
Primes:      0.9789869785308838

In [68]: runSieveOfEratosthenes(ps2)
Sieve tot.:  0.18288207054138184

如您所见,runIsPrime 执行时间随着数组长度的增加而增加,而 runSieveOfEratosthenes 时间大致保持不变。让我们进一步增加数组大小:

In [69]: runIsPrime(ps2 * 10)
Primes:      9.91222095489502

In [70]: runSieveOfEratosthenes(ps2 * 10)
Sieve tot.:  0.2231128215789795

作为结论 - 如果您需要检查一个数字是否为质数,您可以迭代到数字的根 - 这比构建筛子更快。如果你有一个数字列表,你可能想要 运行 一些测试,如果列表很小,你仍然可以使用第一种方法。但是当你有大量数字要检查时,最好构建一个筛子。