【问题标题】:My short recursive function takes too long to execute, how can I optimize it我的短递归函数执行时间太长,我该如何优化它
【发布时间】:2015-02-24 03:16:34
【问题描述】:

我正在尝试在 CodeChef 上解决这个问题:http://www.codechef.com/problems/COINS

但是当我提交我的代码时,它显然需要很长时间才能执行,并且说时间已经过期。我不确定我的代码是否效率低下(对我来说似乎不是这样)或者我是否遇到了 I/O 问题。有 9 秒的时间限制,解决最多 10 个输入,0

在 Byteland 他们有一个非常奇怪的货币体系。

每个 Bytelandian 金币上都写有一个整数。一枚硬币 n可以在银行兑换成三种硬币:n/2n/3n/4。但 这些数字都被四舍五入了(银行必须盈利)。

您也可以用美元出售 Bytelandian 硬币。找的零钱 比率为 1:1。但是你不能购买 Bytelandian 硬币。

你有一枚金币。美元的最高金额是多少 能买到吗?

这是我的代码:输入 1 000 000 000 似乎需要很长时间

def coinProfit(n):
    a = n/2
    b = n/3
    c = n/4

    if a+b+c > n:
        nextProfit = coinProfit(a)+coinProfit(b)+coinProfit(c)
        if nextProfit > a+b+c:
            return nextProfit
        else:
            return a+b+c

    return n

while True:
    try:
        n = input()
        print(coinProfit(n))
    except Exception:
        break

【问题讨论】:

  • 另外,如果我说我的函数的复杂性将增加 3 的幂,我是否正确?即 3^log_4(n) 是最坏的情况吗?
  • 我敢打赌 input() 是你的瓶颈。但是 1G 的 9 秒是相当公平的时间。
  • 这个程序永远不会退出,while 循环将永远继续要求更多输入。我错过了什么吗?
  • 当我在最后删除 try/except 块后尝试你的程序时,我每次都有一个maximum recursion depth 错误,不管n
  • @superultranova,不是真的。 except 分支中的 break 将退出 while 循环。

标签: python performance recursion


【解决方案1】:

问题在于您的代码将每个递归调用分支成三个新调用。这会导致指数行为。

不过,好消息是大多数调用都是重复的:如果你用40 调用coinProfit,这将级联到:

coinProfit(40)
 - coinProfit(20)
    - coinProfit(10)
    - coinProfit(6)
    - coinProfit(5)
 - coinProfit(13)
 - coinProfit(10)

你看到的是重复了很多努力(在这个小例子中,coinProfit 已经在 10 上调用了两次)。

您可以使用动态编程来解决这个问题:存储较早的计算结果,防止您在此部分上再次分支。

他/她自己可以实现动态编程,但可以使用@memoize 装饰器自动执行此操作。

现在这个函数做了很多工作太多次了。

import math;

def memoize(f):
    memo = {}
    def helper(x):
        if x not in memo:            
            memo[x] = f(x)
        return memo[x]
    return helper

@memoize
def coinProfit(n):
    a = math.floor(n/2)
    b = math.floor(n/3)
    c = math.floor(n/4)
    if a+b+c > n:
        nextProfit = coinProfit(a)+coinProfit(b)+coinProfit(c)
        if nextProfit > a+b+c:
            return nextProfit
        else:
            return a+b+c
    return n

@memoize 将函数转换为:对于函数,维护已计算输出的数组。如果对于给定的输入,已经计算了输出,则将其存储在数组中,并立即返回。否则,它会按照您的方法定义进行计算,存储在数组中(供以后使用)并返回。

正如@steveha 所指出的,python 已经内置了一个名为memoize 的函数lru_cache,可以在here 找到更多信息。


最后一点是@memoize 或其他动态编程 构造并不是所有效率问题的解决方案。首先@memoize可以对副作用产生影响:假设你的函数在stdout上打印一些东西,然后@memoize这将影响打印的次数.其次,有像 SAT 问题这样的问题,@memoize 根本不起作用,因为上下文本身是指数的(据我们所知)。这样的问题被称为NP-hard

【讨论】:

  • 您可以使用 lru_cache() 装饰器(在 Python 3.2 中添加到 Python 的 functools),而不是编写自己的 memoize 函数。对于早于 3.2 的 Python,您可以获得实现它的配方:stackoverflow.com/questions/11815873/…
  • @steveha:更新了一个小评论。我认为如何实现模式不是问题的重点,更多的是背后的想法。但无论如何,还是不错的评论;)。
  • @steveha 注意到 lru_cache 不带参数使用的默认最大大小为 128 个值。必须设置参数maxsize 以防您需要更多(这里就是这种情况)。
  • @Jivan 我仍然认为使用标准 lru_cache() 而不是自己滚动是一种胜利,即使您确实需要设置 maxsize
  • @steveha 我没说最好自己动手。绝对使用lru_cache(),但如果需要,请不要忘记设置max_size :)
【解决方案2】:

您可以通过将结果存储在某种cache 中来优化程序。所以如果结果存在于cache中,则不需要进行计算,否则计算并将值放入cache中。通过这种方式,您可以避免计算已经计算的值。例如

cache = {0: 0}


def coinProfit(num):
    if num in cache:
        return cache[num]
    else:
        a = num / 2
        b = num / 3
        c = num / 4
        tmp = coinProfit(c) + coinProfit(b) + coinProfit(a)
        cache[num] = max(num, tmp)
        return cache[num]


while True:
    try:
        print coinProfit(int(raw_input()))
    except:
        break

【讨论】:

    【解决方案3】:

    我只是尝试并注意到一些事情......这不必被视为答案。

    在我的(最近的)机器上,使用 n = 100 000 000 进行计算需要 30 秒。我想这对于您刚刚编写的算法来说是很正常的,因为它会一次又一次地计算相同的值(您没有像其他答案中建议的那样使用缓存优化递归调用)。

    此外,问题定义非常温和,因为它坚持:每个 Bytelandian 金币上都写有一个整数,但这些数字都是向下取整的。知道了这一点,您应该将函数的前三行变成:

    import math
    
    def coinProfit(n):
        a = math.floor(n/2)
        b = math.floor(n/3)
        c = math.floor(n/4)
    

    这将防止 a, b, c 被转换为 float 数字(至少是 Python3),这将使您的计算机疯狂地陷入大递归混乱,即使是 n 的最小值。

    【讨论】:

    • 更好的是使用 Python 的 // 运算符,整数除法:a = n // 2 等等。甚至在 Python 2.x 中也可用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-11
    • 1970-01-01
    • 2017-06-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多