2的幂的python itertools排列太慢了

Posted

技术标签:

【中文标题】2的幂的python itertools排列太慢了【英文标题】:python itertools permutations for powers of 2 is too slow 【发布时间】:2018-04-12 11:23:13 【问题描述】:

我遇到了一个非常奇怪的问题,似乎找不到解决方法。

以下代码找到n 的素因数分解,将素因数放入一个列表中,然后找到所有可能的素因数总和变化,并打印出该列表的唯一值。

示例:44 的质因数是 2*2*11,所以 44 会打印出来

2,2+2,11,2+11,2+2+11 = 2,4,11,13,15:

这是我的代码:

import math
import sys
import itertools
from itertools import permutations

def primes(n):
    primfac = []
    d = 2
    while d*d <= n:
        while (n % d) == 0:
            primfac.append(d)
            n //= d
        d += 1
    if n > 1:
       primfac.append(n)
    return primfac


def primecombo(n):
    b = []
    for i in range(1, len(primes(n))+1):
        for subset in permutations(primes(n), i):
            b.append(sum((subset)))
    a = list(set(b))
    a.sort()
    return a

代码本身在大多数情况下似乎运行良好且高效,但由于某些非常奇怪的原因,当您处理任何唯一素因数为 2 的数字时,它变得异常缓慢。

如果您尝试 print primecombo(444444) 或 print primecombo(23452823),它几乎会立即打印结果,但如果您尝试 2048 或 4096,它会变得非常慢。

谁能明白为什么会出现这种情况以及我可以做些什么来解决它?

【问题讨论】:

23452823 只有 7 个因子,而 2048 有 11 个因子。由于得到排列是指数的,所以它的时间长得令人难以忍受。此外,您在每次迭代时都重新计算素数,而是将它们存储在一个变量中。 另外,你不需要你的素数的排列,只需要组合。这应该会显着缩短您的时间。 感谢 Olivier 大大加快了速度 当一个因子存在多次时,可以通过使用因子计数器并迭代其组合来加快速度,如果没有人这样做,我稍后会写一个完整的答案 【参考方案1】:

简答

使用itertools.permutations 可使您的算法对素因子的冗余分区求和。使用itertools.combinations 应该是一个相当大的改进,但我们仍然可以做得更好。

长答案

使用itertools.permutations 查找所有排列使您的函数primecombo 在因子数量方面运行在阶乘时间内,比指数最差。

让我们看一下关于因子 k 数量的时间复杂度。主要步骤是迭代permutations(primes(n), len(primes(n))。有 k! 个排列,而您正在对每个排列求和。因此,您的算法的时间复杂度是

O(k * k!)

这就是为什么有 11 个因子的 2048 比有 7 个因子要处理的 23452823 长得多。

另类

幸运的是,不必访问每个排列。例如,如果您有因子 2、3 和 4,您将对 2、3 和 4 的每个排列求和,这是多余的。一种快速的改进是对组合求和,但即便如此,当存在不止一次出现的因子时,我们有时也会对同一个分区求和两次。

以下解决方案通过使用Counter 而不是list 跟踪主要因素来解决此问题。这稍后允许我们使用itertools.product

此算法能够在几毫秒内找到 4096 所需的总和,请参阅下面的时间复杂度分析。

import itertools
from collections import Counter

def primes(n):
    primfac = Counter()
    d = 2

    while d ** 2 <= n:
        while (n % d) == 0:
            primfac[d] += 1
            n //= d
        d += 1

    if n > 1:
       primfac[n] += 1

    return primfac

def primecombo(n):
    factor_sums = [[p * e for e in range(exp + 1)] for p, exp in primes(n).items()]

    sums = set(sum(partition) for partition in itertools.product(*factor_sums))

    return sums

primecombo(4096) # 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24

时间复杂度

时间复杂度取决于主要因素的分布。如果有 k 个不同的因素,就会出现最坏的情况。我们的itertools.product 的大小为 2k。从而使算法

O(k * 2k)

【讨论】:

能否请您在您的好答案中添加 OP 原始代码和您的代码的运行时间?

以上是关于2的幂的python itertools排列太慢了的主要内容,如果未能解决你的问题,请参考以下文章

查找数字是不是为 2 的幂的时间复杂度

如何使用递归计算幂的幂? [关闭]

python itertools 具有绑定值的排列

python itertools模块实现排列组合

51 NOD 1013 3的幂的和

51Nod - 1013 - 3的幂的和(分治快速幂)