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

Python 我的短递归函数执行时间太长,如何优化它,python,performance,recursion,Python,Performance,Recursion,我正在尝试在CodeChef上解决此问题: 但是当我提交我的代码时,它显然需要太长的时间来执行,并且说时间已经过了。我不确定我的代码是否效率低下,我觉得不是这样,或者我在I/O方面有问题。有9秒的时间限制,最多解决10个输入,0问题是你的代码将每个递归调用分支为三个新的。这会导致指数行为 然而,最妙的是,大多数调用都是重复的:如果您使用40调用coinProfit,这将级联到: coinProfit(40) - coinProfit(20) - coinProfit(10) -

我正在尝试在CodeChef上解决此问题:


但是当我提交我的代码时,它显然需要太长的时间来执行,并且说时间已经过了。我不确定我的代码是否效率低下,我觉得不是这样,或者我在I/O方面有问题。有9秒的时间限制,最多解决10个输入,0问题是你的代码将每个递归调用分支为三个新的。这会导致指数行为

然而,最妙的是,大多数调用都是重复的:如果您使用40调用coinProfit,这将级联到:

coinProfit(40)
 - coinProfit(20)
    - coinProfit(10)
    - coinProfit(6)
    - coinProfit(5)
 - coinProfit(13)
 - coinProfit(10)
您看到的是,在这个小示例中重复了大量的工作,coinProfit已经在10上调用了两次

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

可以自己实现动态编程,但可以使用@memoize decorator自动实现

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

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,可以找到更多信息

最后一点需要注意的是@memoize或其他动态编程结构并不是所有效率问题的解决方案。首先@memoize会对副作用产生影响:假设你的函数在stdout上打印一些东西,那么@memoize会影响打印的次数。其次,还有像SAT这样的问题,@memoize根本不起作用,因为据我们所知,上下文本身是指数级的。这类问题称为NP难问题


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

然而,最妙的是,大多数调用都是重复的:如果您使用40调用coinProfit,这将级联到:

coinProfit(40)
 - coinProfit(20)
    - coinProfit(10)
    - coinProfit(6)
    - coinProfit(5)
 - coinProfit(13)
 - coinProfit(10)
您看到的是,在这个小示例中重复了大量的工作,coinProfit已经在10上调用了两次

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

可以自己实现动态编程,但可以使用@memoize decorator自动实现

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

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,可以找到更多信息

最后一点需要注意的是@memoize或其他动态编程结构并不是所有效率问题的解决方案。首先@memoize会对副作用产生影响:假设你的函数在stdout上打印一些东西,那么@memoize会影响打印的次数。其次,还有像SAT这样的问题,@memoize根本不起作用,因为据我们所知,上下文本身是指数级的。这类问题称为NP难问题


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

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

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

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

我只是试着注意到了一些事情。。。这不一定是答案

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

此外,问题的定义相当温和,因为它坚持:每枚比特兰金币上都有一个整数,但这些数字都是向下舍入的。了解这一点后,您应该将函数的前三行转化为:

import math

def coinProfit(n):
    a = math.floor(n/2)
    b = math.floor(n/3)
    c = math.floor(n/4)
这将防止a、b、c在l处变成浮点数Python3
这会让你的计算机疯狂地陷入一个大的递归混乱,即使是最小的n值。

我只是尝试了一下,注意到了一些事情。。。这不一定是答案

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

此外,问题的定义相当温和,因为它坚持:每枚比特兰金币上都有一个整数,但这些数字都是向下舍入的。了解这一点后,您应该将函数的前三行转化为:

import math

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


这将防止a、b、c变成浮点数Python3,至少这会让你的计算机疯狂地陷入一个大的递归混乱,即使是最小的n值。

还有,如果我说我的函数的复杂性将增加三倍,我说的对吗?也就是说,最坏的情况是3^logn?我打赌输入是你的瓶颈。但是1G的9秒是相当公平的时间。这个程序永远不会退出,而while循环将永远继续要求更多的输入。我遗漏了什么吗?当我在最后删除try/except块后尝试你的程序时,我每次都有一个最大的递归深度错误,不管怎样n@superultranova,不是真的。except分支中的中断将退出while循环。另外,如果我说函数的复杂性将增加三次方,那么我是否正确?也就是说,最坏的情况是3^logn?我打赌输入是你的瓶颈。但是1G的9秒是相当公平的时间。这个程序永远不会退出,而while循环将永远继续要求更多的输入。我遗漏了什么吗?当我在最后删除try/except块后尝试你的程序时,我每次都有一个最大的递归深度错误,不管怎样n@superultranova,不是真的。except分支中的中断将退出while循环。您可以使用添加到Python 3.2中的functools中的lru_缓存装饰器,而不是编写自己的memoize函数。对于早于3.2的Python,您可以得到一个实现它的配方:@steveha:更新了一个小注释。我认为模式是如何实现的不是问题的重点,而是它背后的想法。但不管怎么说,评论不错@steveha注意到,不带参数使用的lru_缓存的默认最大大小为128个值。必须设置参数maxsize,以防您需要更多,这里就是这种情况。@Jivan我仍然认为使用标准lru_缓存而不是滚动自己的缓存是一种胜利,即使您确实需要设置maxsize。@steveha我没有说滚动自己的缓存更好。当然要使用lru_缓存,但如果需要,不要忘记设置max_size:您可以使用添加到Python 3.2中的functools中的lru_缓存装饰器,而不是编写自己的memoize函数。对于早于3.2的Python,您可以得到一个实现它的配方:@steveha:更新了一个小注释。我认为模式是如何实现的不是问题的重点,而是它背后的想法。但不管怎么说,评论不错@steveha注意到,不带参数使用的lru_缓存的默认最大大小为128个值。必须设置参数maxsize,以防您需要更多,这里就是这种情况。@Jivan我仍然认为使用标准lru_缓存而不是滚动自己的缓存是一种胜利,即使您确实需要设置maxsize。@steveha我没有说滚动自己的缓存更好。当然要使用lru缓存,但如果需要的话,不要忘记设置max_size:更好的方法是使用Python的//运算符、整数除法:a=n//2等等。甚至在Python2.x中也可用。更好的方法是使用Python的//运算符、整数除法:a=n//2等等。甚至在Python2.x中也可用。