我的短递归函数执行时间太长,我该如何优化它

时间:2014-12-27 00:44:33

标签: python performance recursion

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

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

  

在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 个答案:

答案 0 :(得分:9)

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

但不错的是,大多数电话都是重复的:如果您使用coinProfit拨打40,这将级联为:

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

答案 1 :(得分:1)

您可以通过将结果存储在某种cache中来优化程序。因此,如果结果存在于cache中,则无需执行计算,否则计算并将值放入cache。通过这种方式,您可以避免计算已计算的值。 E.g。

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

答案 2 :(得分:0)

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

在我(最近)的机器上,用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),这会使您的计算机疯狂变成一个大的递归混乱,即使{{1}的值最小}。