为什么我的埃拉托色尼筛法 运行 这么慢?
Why is my Sieve of Eratosthenes running so slowly?
我正在 Python 中为埃拉托色尼筛法编写素数程序。虽然它看起来有效,但速度很慢。我怎样才能加快速度?
primes = []
upperLimit = 1000
for x in range(2,upperLimit):
primes.append(x)
for y in range(0,int(len(primes)**0.5)):
remove = []
for j in range(primes[y]**2,upperLimit,primes[y]):
remove.append(j)
for i in remove:
if i in primes:
primes.remove(i)
print(primes)
更新:
多亏了答案的帮助,我使用布尔值而不是数字重写了代码。低于 100000 的列表现在运行不到 6 秒。
i = 2
limit = 100000
primes = [True] * limit
primes[0] = False
while i < limit**0.5:
if primes[i-1]:
for x in range(i * 2, limit + 1,i):
primes[x-1] = False
i += 1
count = 1
total = []
for i in primes:
if i:
total.append(count)
count += 1
print(total)
速度很慢,因为您执行的操作比需要的次数多得多。合数 N 的寿命看起来像这样:
- 追加 N 到 素数
- 对于每个 small 数字 I ( < sqrt(limit) )
- 将 I 的所有 large 倍数附加到 remove
- 对于中的每个I删除
- 如果I还在primes,删除它。
每个数字都有很多 "touches"。还有很多 small 个数字需要考虑。
相反,试试这个:
- 制作一个布尔值列表(所有 True),每个潜在素数一个。
- 虽然最低值 I 标记为 True < sqrt(limit):
- 清除(转False)I
的所有大倍数
- 注意:不用再去检查值是否已经是False
在这一点上,你的质数正是那些仍然标记为 True
的值
我认为您代码中的主要低效率是您维护的 list
素数。尽管可能并不明显,但调用 primes.remove
是一项非常昂贵的操作。它需要遍历 list
以尝试找到您要删除的值,然后它需要通过将所有元素移动到您要查找的元素之后来修改 list
。
例如
l = [0, 1, 2, 3, 4]
l.remove(5) # This has to look at all the elements in l, since 6 isn't there
l.remove(0) # This finds 1 quickly but then has to move every element to the left
埃拉托色尼筛法的一种更传统的方法是使用您正在考虑的所有数字的数组(list
in Python),其中每个元素都是一个布尔值,指示是否数字可能是素数。
模仿上面的例子:
l = [True, True, True, True, True]
l[0] = False # Just goes straight to that element and changes its value
下面是如何编写该代码的示例:
primes = [True] * 100000
# We already know 2 is the first prime
primes[0] = False
primes[1] = False
# Fine to stop at sqrt(len(primes)) here as you're already doing
for i in range(2, len(primes)):
if primes[i]:
for j in range(i**2, len(primes), i):
primes[j] = False
print([n for n, is_prime in enumerate(primes) if is_prime])
您会发现这要快得多,因为索引到 list
并以这种方式更改值非常有效。
我正在 Python 中为埃拉托色尼筛法编写素数程序。虽然它看起来有效,但速度很慢。我怎样才能加快速度?
primes = []
upperLimit = 1000
for x in range(2,upperLimit):
primes.append(x)
for y in range(0,int(len(primes)**0.5)):
remove = []
for j in range(primes[y]**2,upperLimit,primes[y]):
remove.append(j)
for i in remove:
if i in primes:
primes.remove(i)
print(primes)
更新: 多亏了答案的帮助,我使用布尔值而不是数字重写了代码。低于 100000 的列表现在运行不到 6 秒。
i = 2
limit = 100000
primes = [True] * limit
primes[0] = False
while i < limit**0.5:
if primes[i-1]:
for x in range(i * 2, limit + 1,i):
primes[x-1] = False
i += 1
count = 1
total = []
for i in primes:
if i:
total.append(count)
count += 1
print(total)
速度很慢,因为您执行的操作比需要的次数多得多。合数 N 的寿命看起来像这样:
- 追加 N 到 素数
- 对于每个 small 数字 I ( < sqrt(limit) )
- 将 I 的所有 large 倍数附加到 remove
- 对于中的每个I删除
- 如果I还在primes,删除它。
每个数字都有很多 "touches"。还有很多 small 个数字需要考虑。
相反,试试这个:
- 制作一个布尔值列表(所有 True),每个潜在素数一个。
- 虽然最低值 I 标记为 True < sqrt(limit):
- 清除(转False)I 的所有大倍数
- 注意:不用再去检查值是否已经是False
在这一点上,你的质数正是那些仍然标记为 True
的值我认为您代码中的主要低效率是您维护的 list
素数。尽管可能并不明显,但调用 primes.remove
是一项非常昂贵的操作。它需要遍历 list
以尝试找到您要删除的值,然后它需要通过将所有元素移动到您要查找的元素之后来修改 list
。
例如
l = [0, 1, 2, 3, 4]
l.remove(5) # This has to look at all the elements in l, since 6 isn't there
l.remove(0) # This finds 1 quickly but then has to move every element to the left
埃拉托色尼筛法的一种更传统的方法是使用您正在考虑的所有数字的数组(list
in Python),其中每个元素都是一个布尔值,指示是否数字可能是素数。
模仿上面的例子:
l = [True, True, True, True, True]
l[0] = False # Just goes straight to that element and changes its value
下面是如何编写该代码的示例:
primes = [True] * 100000
# We already know 2 is the first prime
primes[0] = False
primes[1] = False
# Fine to stop at sqrt(len(primes)) here as you're already doing
for i in range(2, len(primes)):
if primes[i]:
for j in range(i**2, len(primes), i):
primes[j] = False
print([n for n, is_prime in enumerate(primes) if is_prime])
您会发现这要快得多,因为索引到 list
并以这种方式更改值非常有效。