【问题标题】:Efficiently generate primes in Python and calculate complexity在 Python 中高效生成素数并计算复杂度
【发布时间】:2018-11-10 13:33:26
【问题描述】:

Python 3 生成从1到n的素数。如何提高效率,复杂度如何?

输入:一个数字,最大值(一个大数字) 输出:从 1 到最大值的所有素数 输出为列表形式,为 [2,3,5,7,11,13,.......] 代码试图以一种有效的方式(最低的时间复杂度)来执行这个任务。

from math import sqrt    
max = (10**6)*3
print("\nThis code prints all primes till: " , max , "\n")
list_primes=[2]

def am_i_prime(num):
    """
    Input/Parameter the function takes: An integer number
    Output: returns True, if the number is prime and False if not
    """ 
    decision=True
    i=0
    while(list_primes[i] <= sqrt(num)): #Till sqrt(n) to save comparisons
        if(num%list_primes[i]==0):
            decision=False
            break
                    #break is inserted so that we get out of comparisons faster
                    #Eg. for 1568, we should break from the loop as soon as we know that 1568%2==0
        i+=1
    return decision


for i in range(3,max,2):  #starts from 3 as our list contains 2 from the beginning
    if am_i_prime(i)==True:
    list_primes.append(i)  #if a number is found to be prime, we append it to our list of primes

print(list_primes)

我怎样才能加快速度?我在哪里可以改进? 这段代码的时间复杂度是多少?哪些步骤效率低? Eratosthenes 筛在哪些方面比这更有效?

为最初的几次迭代工作:-

  1. 我们有一个包含素数的list_primes。它最初只包含 2 个。
  2. 我们转到下一个数字 3。3 能被list_primes 中的任何数字整除吗?不!我们将 3 附加到 list_primes。现在,list_primes=[2,3]
  3. 我们转到下一个数字 4。4 能被list_primes 中的任何数字整除吗?是(4 可被 2 整除)。所以,我们什么都不做。现在list_primes=[2,3]
  4. 我们转到下一个数字 5。5 能被list_primes 中的任何数字整除吗?不!我们将 5 附加到 list_primes。现在,list_primes=[2,3,5]
  5. 我们转到下一个数字 6。6 能被 list_primes 中的任何数字整除吗?是的(6 可以被 2 整除,也可以被 3 整除)。所以,我们什么都不做。现在list_primes=[2,3,5]
  6. 等等……

【问题讨论】:

  • FWIW,我会从循环条件中删除 sqrt(num) 的计算。计算成本很高,而且不会改变,所以只计算一次。也许它可以自动优化,但我不会指望它。
  • 是的,有道理。谢谢

标签: algorithm time-complexity primes sieve-of-eratosthenes


【解决方案1】:

有趣的是,要证明您的算法完全正确,需要一个相当深的数学定理。定理是:“对于每个 n ≥ 2,在 n 和 n^2 之间存在一个质数”。我知道它已经被证明了,而且更严格的界限也被证明了,但我必须承认我自己不知道如何证明它。如果这个定理不正确,那么 am_i_prime 中的循环可以越过数组的边界。

质数 ≤ k 是 O (k / log k) - 这又是一个非常深奥的数学定理。再次,超出我的证明。

但是无论如何,大约有 n / log n 个素数,直到 n,对于这些素数,循环将遍历所有素数,直到 n^(1/2),并且有 O (n^(1/2 ) / log n) 个。

因此,仅对于素数而言,运行时间为 O (n^1.5 / log^2 n),因此这是一个下限。通过一些努力,应该可以证明对于所有数字,运行时间是渐近相同的。

O (n^1.5 / log n) 显然是一个上限,但实验上找到所有素数 ≤ n 的除法次数似乎≤ 2 n^1.5 / log^2 n,其中 log 是自然对数.

【讨论】:

  • 是的,这是有道理的。你从哪里得到 O (n^1.5 / log n)? O (n^1.5 / log^2 n) 有意义,因为 n^1.5 / log^2 n = (n^1/2 * log n) * (n / log n) ,这是一个素数所需的时间 *素数个数
  • 我理解我的错误。让我们假设 m = sqrt(n)。在 m 之前有 m/logm 个素数,对于每个数字,我们都在执行 sqrt(m) 比较。因此,(m/logm) * m = m^1.5 / m 是时间复杂度
【解决方案2】:

您的代码的以下重新排列和优化将在您原始代码的近 1/2 时间内达到您的最大值。它将您的顶级循环和谓词函数组合成一个函数,以消除开销并更有效地管理平方(平方根):

def get_primes(maximum):
    primes = []

    if maximum > 1:
        primes.append(2)
        squares = [4]

        for number in range(3, maximum, 2):
            i = 0

            while squares[i] <= number:
                if number % primes[i] == 0:
                    break

                i += 1
            else:  # no break
                primes.append(number)
                squares.append(number * number)

    return primes

maximum = 10 ** 6 * 3
print(get_primes(maximum))

但是,基于筛的算法很容易解决这个问题(因为它避免了除法和/或乘法)。您的代码有一个错误:设置 max = 1 将创建列表 [2] 而不是空列表的正确答案。始终测试极限的两端。

【讨论】:

    【解决方案3】:

    O(N**2)

    大概来说,第一次调用am_I_prime做1比较,第二次做2,...,所以总数是1 + 2 + ... + N,也就是(N * (N-1 )) / 2,它的阶数为 N 平方。

    【讨论】:

    • 请仔细阅读代码。很明显,它只需要 O (N^1.5),但实际上(这需要一点数学)它需要 O (N^1.5) / log^2 N。
    猜你喜欢
    • 1970-01-01
    • 2021-09-06
    • 1970-01-01
    • 1970-01-01
    • 2015-07-17
    • 1970-01-01
    • 2015-12-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多