如何快速计算减少适当分数的数量?

时间:2018-01-23 20:10:20

标签: python algorithm performance

我目前正在解决一个数学问题,我需要计算减少的适当分数的数量,分子和分母的数量都超过1000000(10 ^ 6)。

我的代码适用于小数字;给出的例子(值= 8)给出了正确的(给定的)答案21。

但是由于我不知道的原因,这个代码似乎对于大数字来说非常慢。我在SO上阅读了大量类似的问题,但无法找到任何有用的东西。我仔细查看了thisthis,但这并没有真正帮助我。我的代码以可接受的速度运行,直到1000,然后它变得超级超慢。

import math

def is_prime(n):
    if n == 2:
        return True
    if n % 2 == 0 or n <= 1:
        return False
    sqr = int(math.sqrt(n)) + 1
    for divisor in range(3, sqr, 2):
        if n % divisor == 0:
            return False
    return True

def get_divisors(n):
    liste = [1]
    if n % 2 == 0:
        liste.append(2)
    for divisor in range(3, n+1):
        if n % divisor == 0:
            liste.append(divisor)
    return liste

def intersect(a, b):
    return list(set(a) & set(b))

until = 1000

primes = list()
for i in range(int(until)):
    if i != 1 and i != 0:
        if is_prime(i):
            primes.append(i)

pos = 0
for i in range(1, until+1):
    if i%50 == 0:
        print(i)
    if is_prime(i):
        pos += (i-1)
    else:
        di = get_divisors(i)
        di.remove(1)
        for j in range(1, i):
            dj = get_divisors(j)
            if intersect(di, dj)==[]:
                pos+=1   


print(pos)

我想知道我的程序的哪些部分正在降低速度以及如何解决这些问题。

6 个答案:

答案 0 :(得分:4)

如果素数本身足够快(我仍然建议使用Eratosthenes筛,这比你可以进行大规模生成质数的优质素测试更好),除数的生成不是

for divisor in range(3, n+1):
    if n % divisor == 0:
        liste.append(divisor)

此循环具有O(n)复杂度,因为它可能具有O(n**0.5)这样的复杂性:

liste = set()  # okay, the var name isn't optimal now :)
for divisor in range(3, int((n+1)**0.5)+1):
    if n % divisor == 0:
        other = n // divisor
        liste.add(divisor)
        liste.add(other)

当你找到“低”(&lt; = sqrt(n))除数时,other只是互补的“高”除数。这允许循环更短。

因为您要通过转换为set来执行交集,为什么不首先创建set?优点是,在一个完美的正方形中,你不会两次添加相同的值(你可以测试,但不确定它会更快,因为碰撞是罕见的)

现在一切都是set所以:

if intersect(di, dj)==[]:
            pos+=1   

变为:

if not di.isdisjoint(dj):
   pos += 1
  • 每次迭代都没有创建set
  • set创建,以了解这些集合是否具有公共值。只需使用set.isdisjoint
  • 即可

最后一件事:正如评论中所述,在主循环中测试素数时,您再次调用is_prime(),而不是将生成的素数存储在set中并使用{{1进行测试相反。

答案 1 :(得分:2)

对于初学者,退出进行素数分解。相反,使用Euclid's algorithm检查分子和分母的GCD是否为1。

如果您 需要素数,请搜索更快的生成算法,例如Sieve of Eratosthenes

最后,想一想:生成素数(有效);正如你现在所做的那样,考虑每个分母。但是,您如何根据需要生成有效分子,而不是遍历所有可能的分子?

答案 2 :(得分:1)

可以进行一些简单的优化。您可以尝试这些,看看性能是否足以改善您的用例。

首先,您可以改进查找until以下素数的方式。您可以记住到目前为止已经找到的素数,而不是检查范围内所有数字的范围内的每个数字,而只检查那些直到新的平方根的数字。候选人素数。如果一个数是非素数,它可以被一些小于或等于其平方根的素数整除。由于n以下的素数少于n,因为n越大,你可以通过重复使用已经构建的部分结果来避免大量的除数检查在每一步。这个想法就像做Sieve或Eratosthenes一样,没有写下所有的数字并将它们划掉,而只是写下那些随时都是最佳数字的数字。

其次,当你检查分数是否是最低分时,你不需要寻找任何常见的除数;只检查主要公约数就足够了。为什么?如果分数不是最低的,那么有一个共同的分母可以被一些素数整除。因此,我们可以免除所有因素,并专注于获得主要因素分解。这可以通过仅检查我们在第一步中获得的素数作为候选因子来加速,并且当我们找到匹配时,将每一步除以我们发现的因子。因此,我们也可以避免在这里进行大量检查。像这样:

p = 1
while number > 1 do
    if number % primes[p] == 0 then
        print "Another prime factor is " + primes[p]
        number = number / primes[p]
    else then p = p + 1

这些优化中的第二个可能会让你获得最大的收益。

答案 3 :(得分:1)

生成每个可能分母的主要因子分解。从每个素数因子分解,计算分母(https://en.wikipedia.org/wiki/Euler%27s_totient_function)的总数。这是具有该分母的适当分数的数量。加上所有这些,你就完成了。

答案 4 :(得分:1)

需要从不同的角度来看待这个问题。我们来看看问题。

对于带有a,b,c ...的数字n = a^k1 * b ^ k2 * c ^ k3是素数,问题的答案是n - all number that is less than n and divides by either a or b or c ...

那么,假设我们知道哪个是a, b, c ...,那么如何计算all number that is less than n and divides by a or b or c?让我们看一下包含 - 排除定理,以及这个问题Number of positive integers in [1,1e18] that cannot be divided by any integers in [2,10]。现在,我们可以有一个合理的算法来处理时间复杂度为O(2 ^ m),其中m是素数的数量。假设n 10 ^ 6)

最后,现在要获取每个数字的素数列表,我们可以使用Eratosthenes的Sieve

伪代码:

max = 1000001
// First, get all the prime number factors for each number

list_prime = int[max][]
for i in range (2, max):
    if list_prime[i] = []:
       list_prime[i].append(i)
       for j = i; j < max; j += i
           list_prime[j].append(i)

// Second, calculate the number of value less than i and not share any factor with i

result = 0
for i in range (2, max - 1):
    number_of_prime_factors = length(list_prime[i])
    number_of_value = i
    for j = 1; j < 1 << number_of_prime_factors; j++:
        value = 0;
        for k in range (0, number_of_prime_factors):
            if ((1 << k) & j) != 0 :
                value += i / list_prime[i][k]
        if number of bit in j % 2 == 0:
            number_of_value -= value
        else:
            number_of_value += value
        result +=  number_of_value

return result

时间复杂度应为O(n * 2 ^ m * m),其中m

注意:使用Totient function,时间复杂度应进一步降低到O(n * m),感谢@Matt Timmermans的回答

答案 5 :(得分:1)

我们不需要主要的因素分析或包含 - 排除来解决这个问题。我们可以修改Euler的筛子,使用他的乘积公式动态计算Euler的totient,从而得到O(n)算法。

对于我们累积列表中的每个数字,我们也保存其总数。我们利用了元组(n, phi(n))这一事实,其中phi是Euler的函数:

  if m = p * n, for prime p:
    if p does not divide n
      then phi(m) = (p - 1) * phi(n)
      else phi(m) = p * phi(n)

(注意,通过简单地将指针递增到列表中尚未存在的下一个数字,可以生成素数作为此算法的一部分。)

例如n = 12

list = [(2,1)]
total = 1
prime: 2

2*2: (2 * 2, 2 * 1)
total = total + 2
list: [(2,1), (4,2)]

2*2*2: (2 * 4, 2 * 2)
total = total + 4
list: [(2,1), (4,2), (8,4)]


prime: 3
total = total + 2
list: [(2,1), (3,2), (4,2), (8,4)]

3*3: (3 * 3, 3 * 2)
total = total + 6
list: [(2,1), (3,2), (4,2), (8,4), (9,6)]

3*2: (3 * 2, 1 * (3-1))
total = total + 2
list: [(2,1), (3,2), (4,2), (6,2), (8,4), (9,6)]

3*4: (3 * 4, 2 * (3-1))
total = total + 4
list: [(2,1), (3,2), (4,2), (6,2), (8,4), (9,6), (12,4)]


prime: 5
total = total + 4
list: [(2,1), (3,2), (4,2), (5,4), (6,2), (8,4), (9,6), (12,4)]

5*2: (5 * 2, 1 * (5-1))
total = total + 4
list: [(2,1), (3,2), (4,2), (5,4), (6,2), (8,4), (9,6), (10,4), (12,4)]


prime: 7
total = total + 6
list: [(2,1), (3,2), (4,2), (5,4), (6,2), (7,6), (8,4), (9,6), (10,4), (12,4)]


prime: 11
total = total + 10
list: [(2,1), (3,2), (4,2), (5,4), (6,2), (7,6), (8,4), (9,6), (10,4), (11,10), (12,4)]

Python代码(这段代码实际上是对code的改编,通过btilly,优化我的想法):

n = 8

total = 0
a = [None for i in range(0, n+1)]
s = []
p = 1
while (p < n):
  p = p + 1

  if a[p] is None:
    print("\n\nPrime: " + str(p))
    a[p] = p - 1
    total = total + a[p]
    s.append(p)

    limit = n / p
    new_s = []

    for i in s:
      j = i
      while j <= limit:
        new_s.append(j)
        print j*p, a[j]
        a[j * p] = a[j] * p if (not j % p) else a[j] * (p-1)
        total = total + a[j * p]
        j = j * p
    s = new_s

print("\n\nAnswer: " + str(total) + " => " + str([(k,d) for k,d in enumerate(a)]))