【问题标题】:Why does this make my prime-generating algorithm take longer?为什么这会使我的素数生成算法花费更长的时间?
【发布时间】:2016-12-23 15:33:44
【问题描述】:

我试图找出所有低于 1 000 000 的素数。由于所有非素数都可以分解为素数,我的方法是将素数列表作为 [2, 3] 开始,然后遍历每个数字直到1 000 000。如果一个数字可以被 prime_list 中的任何数字整除,那么它就不是素数,然后我转到下一个数字。如果这个数字不能被 prime_list 中的任何数字整除,那么它一定是素数,并且它被添加到这个列表中。

为了尝试提高效率,我添加了一条语句,仅检查有问题的数字是否可以被该数字平方根以下的值整除。我认为这会减少很多计算时间,但实际上它会使我的程序花费更长的时间。谁能解释一下为什么?

这是我的代码:

import math

import time
start_time = time.time()

prime = [2, 3]

def prime_checker(a):
    for j in prime:
        if j < (int(math.sqrt(a)) +1 ):     /// without this line, the program runs faster
            if a % j == 0:
                return False

for i in range (2, 100000):
    if prime_checker(i) != False:
        prime.append(i)

print prime[-1]

print "Time taken = ", time.time() - start_time 

【问题讨论】:

  • 您正在重复执行一项与j 的值无关的昂贵操作。在循环外计算math.sqrt(a)一次
  • 您可能会做得更好:保持一个最大素数的索引,当平方时,它不会超过当前测试的数字。当您从 i 转到 i+1 时,您可以通过单个无平方根测试来更新此索引。然后您立即知道要尝试的除数范围。

标签: python algorithm optimization primes


【解决方案1】:

为了进一步加快您的算法,请注意 2 是唯一的偶素数。所有其他偶数都是合数。您已经拥有prime = [2, 3],因此您可以从 5 开始搜索(4 不是素数)并且只检查奇数:

for i in range (5, 100000, 2):
    if prime_checker(i) != False:
        prime.append(i)

【讨论】:

    【解决方案2】:

    您重复计算a 的平方根所花费的时间超过了您跳过较大素数所节省的时间。计算平方根一次,在我的测试中,它比重复计算快 10 倍(比完全不考虑这条线快大约 3 倍)。

    def prime_checker(a):
        limit = int(math.sqrt(a)) + 1
        for j in prime:
            if j > limit:
                break
            if a % j == 0:
                return False
        return True
    
    for i in range (2, 100000):
        if prime_checker(i):
            prime.append(i)
    

    【讨论】:

    • 您实际上根本不需要计算平方根;相反,将您要测试的素数平方。
    • 一旦你必须在循环体中平方超过 5 个质数左右,一个平方根仍然会更快。
    • 我很确定平方根的成本远远超过五倍。
    • 可能。但是,关键是对于素数 a,您需要 O(lg (sqrt(a))) 乘法,这可能a 的单个平方根快。
    • 对于 prime a 你需要 O( sqrt(a) / lg(sqrt(a))) 与最佳试验划分算法,但 O(a / lg(a)) 与 OP's。即,OP 跳过较大的素数。甚至您的代码也不是。 :)
    【解决方案3】:

    “如果这个数不能被 prime_list 中的任何数整除”不大于这个数的平方根,那么它就是一个素数。

    一旦你在平方根以上找到一个质数,其余的都将如此。我们提前知道这一点。

    重点不是检查是否避免每个无关检查,而是提前阻止所有检查。这将使您的代码加速 100 倍,如果不是 1000 倍甚至更多。

    换句话说,真正的问题不是sqrt的重复计算,而是限制条件的错误处理。通过正确处理限制条件,即使sqrt 的重复计算也不应该有多大关系。

    正确的方法是:尽快跳出循环,即在达到平方根以上的第一个素数时立即跳出;可能是直接返回True

    【讨论】:

    • 我认为他意识到了这一点,这就是他将比较添加到平方根的原因。问题是他进行了太多的平方根计算,这比检查过大的素数更昂贵。
    • @chepner 他正在做的是 ~ k^2;我建议的是~k^1.5,在k个素数中产生。也许我有点太神秘了。为了清楚起见,我进行了编辑。
    • IOW 与 sqrt 条件的正确处理,即使是 sqrt 的重复计算也应该无关紧要。
    【解决方案4】:

    使用**0.5 而不是math.sqrt 也往往会呈指数级增长:

    >>> import timeit
    >>> print(timeit.timeit('math.sqrt(1830374)', setup='import math', number=10000))
    0.0020401941434329274
    >>> print(timeit.timeit('1830374**0.5', number=10000))
    0.00015091430498159752
    

    不过,Chepner 的代码是正确的答案,只是不要忘记像 rossum 所说的那样进行迭代。他所说的迭代方式实际上可以为您节省 50 万次调用(不过,如果使用 Chepner 的算法,它们会很快中断,这仍然会浪费很多时间)。

    【讨论】:

      【解决方案5】:

      您的解决方案消耗O(N**1.5) 时间。要更快,请使用Sieve of Eratosthenes。它的时间复杂度是O(NloglogN))

      n = 10 ** 5
      sieve = [True] * n
      sieve[0] = sieve[1] = False
      
      for i in range(2, n):
          if sieve[i]:
              for j in range(i + i, n, i):
                  sieve[j] = False
      
      primes = []
      for i, j in enumerate(sieve):
          if j:
              primes.append(i)
      

      【讨论】:

        猜你喜欢
        • 2020-04-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多