繁体   English   中英

生成一个数字的所有除数列表的最快方法

[英]fastest way to produce a list of all divisors of a number

给定一个数字n ,我想生成n的所有唯一除数的排序列表(没有重复)。 解决这个问题真的很简单,但我感兴趣的是效率。 最快的方法是什么?

这是一种方法,使用纯 python:

def get_divisors(n):
    """
    :param n: positive integer.
    :return: list of all different divisors of n.
    """
    if n <= 0:
        return []
    divisors = [1, n]
    for div in range(1, int(n ** 0.5 + 1)):
        if n % div == 0:
            divisors.extend([n // div, div])
    return sorted(list(set(divisors)))

关于如何优化它的任何建议? 欢迎使用 Numpy 和其他优化库。

您已经进行了平方根优化。 接下来是利用 numpy 的并行性:

import numpy as np

def getDivs(N):
    divs = np.arange(1,int(N**0.5)+1)  # potential divisors up to √N
    divs = divs[N % divs==0]           # divisors
    comp = N//divs[::-1]               # complement quotients
    return np.concatenate((divs,comp[divs[-1]==comp[0]:])) # combined
    
print(getDivs(1001**2))
[      1       7      11      13      49      77      91     121     143
     169     539     637     847    1001    1183    1573    1859    5929
    7007    8281   11011   13013   20449   77077   91091  143143 1002001]

comp[divs[-1]==comp[0]:]如果是 integer,则避免重复平方根。

我希望最快的方法是从输入的完整素数分解中“从头开始”构建除数。 但是你不会在这里得到关于“最快的方法”的答案来开始一个素因数分解:这本身就是一个巨大的话题,对于大整数来说仍然是一个非常活跃的研究领域。

下面的方法简单地使用试除法,向上通过剩余因子的平方根,但跳过 2 和 3 的倍数(除了 2 和 3 本身)。 通过 32 位整数,这是相当快的。

给定一个素数分解

n == p0**e0 * p1**e1 * p2**e2 ...

那么n的除数是所有且仅是该形式的整数,其指数小于或等于e0, e1, e2, ... itertools.product()可以直接生成所有这样的指数元组,然后只需执行幂和结果相乘即可。

def get_divisors(n):
    from math import isqrt, prod
    from itertools import accumulate, chain, cycle, product
    if n <= 1:
        return [n]
    ps = []
    exps = []
    limit = isqrt(n)
    for cand in chain([2, 3], accumulate(cycle([2, 4]),
                                               initial=5)):
        if cand > limit:
            break
        if n % cand == 0:
            count = 1
            n //= cand
            while n % cand == 0:
                count += 1
                n //= cand
            ps.append(cand)
            exps.append(count)
            limit = isqrt(n)
    if n > 1:
        ps.append(n)
        exps.append(1)
    result = []
    for pows in product(*(range(exp + 1) for exp in exps)):
        result.append(prod(p**e for p, e in zip(ps, pows)))
    return sorted(result)

>>> for i in range(22):
...     print(i, get_divisors(i))
0 [0]
1 [1]
2 [1, 2]
3 [1, 3]
4 [1, 2, 4]
5 [1, 5]
6 [1, 2, 3, 6]
7 [1, 7]
8 [1, 2, 4, 8]
9 [1, 3, 9]
10 [1, 2, 5, 10]
11 [1, 11]
12 [1, 2, 3, 4, 6, 12]
13 [1, 13]
14 [1, 2, 7, 14]
15 [1, 3, 5, 15]
16 [1, 2, 4, 8, 16]
17 [1, 17]
18 [1, 2, 3, 6, 9, 18]
19 [1, 19]
20 [1, 2, 4, 5, 10, 20]
21 [1, 3, 7, 21]

由于假设输入不大于 10 亿,因此您可以使用Wheel 分解(以{2, 3}为基础)计算主要因子,这是对基本试验除法的改进。 这很快,因为素数因子的数量总是很少(不超过 30 个值)。 然后,您可以将主要因素转换为除数列表(可能有数千个项目)。 使用Numba 即时编译器(JIT) 可以有效地计算分解。 这是生成的代码:

import numba as nb

@nb.njit('List(int_)(int_)')
def get_prime_divisors(n):
    divisors = []
    while n % 2 == 0:
        divisors.append(2)
        n //= 2
    while n % 3 == 0:
        divisors.append(3)
        n //= 3
    i = 5
    while i*i <= n:
        for k in (i, i+2):
            while n % k == 0:
                divisors.append(k)
                n //= k
        i += 6
    if n > 1:
        divisors.append(n)
    return divisors

@nb.njit('List(int_)(int_)')
def get_divisors(n):
    divisors = []
    if n == 1:
        divisors.append(1)
    elif n > 1:
        prime_factors = get_prime_divisors(n)
        divisors = [1]
        last_prime = 0
        factor = 0
        slice_len = 0
        # Find all the products that are divisors of n
        for prime in prime_factors:
            if last_prime != prime:
                slice_len = len(divisors)
                factor = prime
            else:
                factor *= prime
            for i in range(slice_len):
                divisors.append(divisors[i] * factor)
            last_prime = prime
        divisors.sort()
    return divisors

以下是我机器上 5000 个介于 1 到 100 万之间的随机整数的计时:

Initial get_divisors:        125 ms
Alain's getDivs:              40 ms
Tim Peters' get_divisors:     87 ms
This solution:                 7 ms

以下是我机器上 2000 个介于 1 到 10 亿之间的随机整数的计时:

Initial get_divisors:       1403 ms
Alain's getDivs:             231 ms
Tim Peters' get_divisors:    178 ms
This solution:                 8 ms

因此,该解决方案比最快的替代解决方案快 6~22 倍,比初始实施快 18~175 倍

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM