【问题标题】:Optimize non-abundant sums algorithm优化非丰和算法
【发布时间】:2011-02-03 03:34:31
【问题描述】:

我正在尝试解决this Project Euler question:

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

如果一个数 n 的真因数之和为 小于n,如果这个总和超过n,则称为丰富。

因为 12 是最小的丰度数,1 + 2 + 3 + 4 + 6 = 16,所以 可以写成两个丰富数之和的最小数 是 24。通过数学分析,可以证明所有的整数 大于 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 左右),并且每次都遍历丰富数字列表,从当前数字中减去每个,然后根据丰富列表检查该答案数字。如果匹配,则循环中断并再次尝试下一个数字,一直到限制。

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

【问题讨论】:

  • 你当前的解决方案是O(m * n),其中m(极限)远大于n(低于极限的丰富数)。现在,计算所有其他丰富数之和的数字的时间复杂度是多少?这比您当前的解决方案更好还是更差?
  • 为什么要对除数进行排序?
  • 一旦您提交了正确的解决方案,您就可以访问该问题的讨论线程,在那里您会找到一些最有效的解决方案。
  • S. Lott,出于某种原因,我懒洋洋地在末尾添加了 1。修好了,谢谢。 kefeizhou,我通读了讨论线程,在那里我看到了很多解决方案,但很少有解释。先进的、优化的解决方案对我来说都非常难以理解……:/
  • @crayZsaaron:这与问题没有直接关系,但是我觉得我应该提到您的代码中的一些小问题——比任何东西都更美观。首先,AbSum() 中的x = 0 行不是必需的; x 在for 语句中定义。其次,您可以(在python中)在for循环的末尾使用else语句,而不是使用本地AbSum变量来存储if i - x in Abundant[:i]:的结果,该语句在循环的末尾执行only 如果 break 没有被调用 - 即只有在没有找到匹配项时循环。

标签: python


【解决方案1】:

您正在针对每个丰富的数字测试 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 很慢。

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

【讨论】:

    【解决方案2】:
        for x in Abundant:
            if i - x in Abundant[:i]:
                AbSum = 1
                break
    

    请注意,这里的 in 表达式需要 O(i) 时间,因此循环是 O(n²)。如果您使用 set 而不是 list,则可以将其改进为 O(n)。

    【讨论】:

      【解决方案3】:

      你可以使用一个简单的数学技巧:不能写成的所有数之和是两个丰富数之和,是所有数之和减去可以写成两个丰富数之和的数:

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

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

      你已经有了一个丰富数字的列表,所以创建一组数字相对容易,可以写成两个丰富数字的总和,并且总和小于限制。只需确保您没有重复的数字,Python set 会这样做。

      【讨论】:

        【解决方案4】:

        让我们从一点点搜索开始,找出无法表达的最大数,因为两个丰富数之和实际上是 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 秒。

        【讨论】:

        • 非常有助于解决问题,帮助我找出哪里出错/效率低下。谢谢!
        【解决方案5】:

        一旦大量数字大于您正在测试的数字,就可以摆脱您的内部循环。

        我也看不懂你的这段代码:

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

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

        【讨论】:

        • Divs 函数计算所有小于或等于数字平方根的除数,然后使用这些除数计算剩余的除数。该测试是为了确保平方根本身没有重复计算。
        • 感谢 Divs 功能提示!至于您询问的部分:模数检查仅运行到数字的平方根,因此在该特定部分中,如果您愿意,我将添加数字的“倒数”。您是在质疑该部分的第二行吗?这就是我要确保它不是平方根的地方,因为这是我能想到的让它工作的全部。我想这是非常草率的。
        • 好的,很公平。自从我做这样一个不涉及素数的问题以来已经有一段时间了。您可能会做得更好,方法是使用集合并立即添加倒数,并让集合的自然属性删除重复项。
        【解决方案6】:

        您的代码看起来可能受益于映射、过滤器或列表理解,有利于那些 for 循环。

        【讨论】:

          猜你喜欢
          • 2016-04-16
          • 2016-07-30
          • 2023-02-03
          • 1970-01-01
          • 2017-07-23
          • 1970-01-01
          • 1970-01-01
          • 2020-02-09
          相关资源
          最近更新 更多