优化非丰富和算法

时间:2011-02-03 03:34:31

标签: python

我正在尝试解决this Project Euler question

  

完美数字是其适当除数之和的数字   完全等于数字。例如,正确的总和   28的除数是1 + 2 + 4 + 7 + 14 = 28,这意味着28   是一个完美的数字。

     

如果正确的除数之和为,则数字n称为不足   小于n,如果此总和超过n,则称为丰富。

     

由于12是最小的数,1 + 2 + 3 + 4 + 6 = 16,所以   最小的数字,可以写成两个丰富数字的总和   通过数学分析,可以显示所有整数   大于28123可写为两个数字的总和。   但是,通过分析不能进一步降低该上限   即使知道最大数量是不可能的   表示为两个有限数的总和小于此限制。

     

求出不能写成的所有正整数的总和   两个数字的总和。

我的解决方案:

#returns a list of the divisors of a given number
def Divs(Number):
    Divisors = []

    for i in range(2 , int(Number**0.5) + 1):
        if Number % i == 0:
            Divisors.append(i)

    for q in range(len(Divisors)):
        if Divisors[q] != (Number / Divisors[q]):
            Divisors.append(Number / Divisors[q])

    Divisors.insert(0,1)
    return Divisors

#returns a list of abundant numbers up to and including the limit
def AbList(limit):
    Abundant = []

    for i in range(11,limit + 1):
        if sum(Divs(i)) > i:
            Abundant.append(i)

    return Abundant

#Finds the sum of all positive integers that cannot be written as the
#sum of two abundant numbers...
def AbSum(limit):
    Abundant = AbList(limit)
    NoAbSum = 0
    for i in range(1 , limit):
        AbSum = 0
        x = 0
        for x in Abundant:
            if i - x in Abundant[:i]:
                AbSum = 1
                break
        if AbSum == 0:
            NoAbSum += i
    return NoAbSum

这需要我的3.4 GhZ处理器大约15分钟才能解决,我正在寻找更好的方法。我并不关心前两个函数,因为它们共同运行不到一秒钟。第三个功能是踢球者。它运行的数字范围达到极限(在这种情况下,20000-something),每次,它运行在大量数字列表中,从当前数字中减去每个数字,然后根据丰富的列表检查该答案数字。如果匹配,则循环中断并再次尝试下一个数字,一直到限制。

我知道必须有更好的方法来做到这一点,但我对编程有点新意。我怎样才能加快这个算法的速度?

6 个答案:

答案 0 :(得分:9)

你正在测试1和极限之间的每个数字(比方说30000),而不是每个数字,所以你要做大约30000 * 7428次迭代;并且您正在检查结果是否在列表中,这是一个非常慢的操作 - 它检查列表中的每个项目,直到找到匹配为止!

相反,你应该生成每个数字,它是两个数字的总和。最多,这将需要7428 * 7428次迭代 - 如果正确执行则更少(提示:避免通过确保b总是> = a来检查a + b和b + a;并且正如其他人建议的那样,请务必当总和太大时停止)。将这些数字标记在limit以下的数字列表中,并将剩余数字相加。

换句话说:

[... 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43 ...]

变为

[... 31, 0, 33, 34, 35, 0, 37, 0, 39, 0, 41, 0, 43 ...]

编辑:在玩了几分钟的实现后,我可以自信地说if i - x in Abundant[:i]:就是问题所在。发布到Project Euler的p23论坛的第一个python解决方案本质上是一个聪明的算法实现,唯一的主要区别是它使用了大量数字的 set 而不是列表。它在15秒内解决了Atom处理器上的问题;当我改变它使用列表,十五分钟后,它仍然没有解决问题。

故事的道德:x in list很慢。

但是,直接生成总和比减去和检查要快。 :)

答案 1 :(得分:5)

    for x in Abundant:
        if i - x in Abundant[:i]:
            AbSum = 1
            break

请注意,in表达式需要O(i)时间,因此循环为O(n²)。如果您使用set而不是list,则可以将此值提高到O(n)。

答案 2 :(得分:3)

你可以使用一个简单的数学技巧:无法写入的所有数字的总和是两个数字之和的总和是所有数字的总和减去可以写成的数字是两个数字的总和:

 solution = sum(range(limit)) - sum(all_two_sums(abundant_numbers))

sum(range(limit))也可以用数学简化,但除非你是高斯,否则你可能找不到它; - ))

你已经有了一个包含大量数字的列表,因此创建一组数字是相对容易的,可以写成两个数字的总和,并且总和小于限制。只要确保没有重复的数字,Python set即可。

答案 3 :(得分:2)

如果丰富的数字大于您正在测试的数字,那么有一点可能有助于摆脱内循环。

我也不明白你的代码:

 for q in range(len(Divisors)):
    if Divisors[q] != (Number / Divisors[q]):
        Divisors.append(Number / Divisors[q])

一旦你确认模数为0,它就是一个除数。我不知道你为什么要进行身份检查。

答案 4 :(得分:2)

您的代码看起来可能会受益于地图,过滤器或列表理解,而不是那些for循环。

答案 5 :(得分:2)

让我们从搜索一下开始,找出最大数字不可表达,因为两个丰富数字的总和实际上是20161.然后,我们可以通过简单的集合成员测试来解决问题。而且,它运行得非常快。 : - )

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from math import sqrt

def d(n):
    sum = 1
    t = sqrt(n)
    # only proper divisors; start from 2.
    for i in range(2, int(t)+1):
        if n % i == 0:
            sum += i + n / i
    # don't count the square root twice!
    if t == int(t):
        sum -= t
    return sum

limit = 20162
sum = 0
# it's a set, after all. sets are faster than lists for our needs.
abn = set()
for n in range(1, limit):
    if d(n) > n:
        abn.add(n)
    # if the difference of the number we're examining and every number in the set
    # is in the set, then the number is the sum of two abundant numbers.
    # otherwise, we must add it to our sum in question.
    if not any( (n-a in abn) for a in abn ):
        sum += n

基于timeit在i5上平均运行0.6463340939061518秒。