在递归中存储返回的值?

时间:2015-09-20 04:00:14

标签: python algorithm recursion

我在理解递归方面遇到了一些困难,因为在某些情况下我认为它确实有意义但在其他情况下我觉得很难理解。我知道递归可以帮助将问题分解为更容易解决的子问题,然后可以结合这些子问题的解决方案来获得我们试图解决的主要问题的主要解决方案。例如,我们有代码来查找n的Fibonnaci和。当然,这不是最快的实现,因为它会导致许多重新计算。

def fib(n):
    """Assumes n is an int >= 0
    Returns Fibonacci of n"""
    if n == 0 or n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

在这种情况下,我了解发生了什么,因为 返回的值在解决基本情况后在后续的其他语句中存储并重用 ,对于fib(2)的情况,fib(1)+ fib(0)将返回2,然后fib(2)的结果用于计算fib(3),其中fib(3)= fib(2) )+ fib(1),我们已经有了答案。这对我来说很清楚,因为在最后一个基本案例(即递归调用高于基本案例)之后返回的结果正在被重用,最终目标是得到答案。

但是,在某些情况下,我发现递归并不是那么简单,这让我很困惑。例如,我们有这段代码:

def maxVal(toConsider, avail):
    """Assumes toConsider a list of items, avail a weight
    Returns a tuple of the total weight of a solution to the
    0/1 knapsack problem and the items of that solution"""

    if toConsider == [] or avail == 0:
        result = (0, ())
    elif toConsider[0].getWeight() > avail:
    # Explore right branch only
        result = maxVal(toConsider[1:], avail)
    else:
        nextItem = toConsider[0]
    # Explore left branch
    withVal, withToTake = maxVal(toConsider[1:],
                                 avail - nextItem.getWeight())
    withVal += nextItem.getValue()
    # Explore right branch
    withoutVal, withoutToTake = maxVal(toConsider[1:],
                                       avail)
        # Choose better branch
        if withVal > withoutVal:
            result = (withVal, withToTake + (nextItem,))
        else:
            result = (withoutVal, withoutToTake)
    return result

我不知道的是,在调用基本案例之后,返回的结果在后续递归调用中的使用位置是什么?似乎下面的递归调用的结果永远不会与其他递归调用相关联 - 这是真的吗?这与上面看到的Fibonacci递归的情况不同。例如,一旦我到达了具有toConsider = []或avail == 0的基本情况,我的结果将是(0,()),然后我返回此结果。但是,最后一个基本案例的结果将如何用于倒数第二个倒数第二次递归?再推断一下,似乎第三次递归与第二次递归无关,第四次递归与第三次递归无关,依此类推......直到主要解决方案。但当然显然不是这种情况。我理解代码在肤浅的意义上是如何工作的,这意味着它描述了在决策树中从上到下,到最后一个基本情况(或节点的叶子)的确切内容,如果我们得到答案就有意义无论如何。但是每次递归的结果如何得到 存储 ,这样最终的结果将反映出已经发生的许多递归中的答案?

此外,是否有多种理解或观点来理解递归? Fibonacci示例可以提供一个实例,其中从下到上看到递归以解决问题,但决策树提供了从上到下查看事物的视角。这意味着我们沿着决策树走下去,得到每个答案,直到我们已经知道解决方案的最后一个基本案例答案,然后我们总结所有这些答案以得到我们想要的最终答案。它是否正确?这是理解递归的两种主要方式 - 从下到上还是自上而下?

上述代码来自麻省理工学院的计算机科学入门书。我目前正在独立学习计算机科学,我真的希望你们能帮助我。谢谢! :d

1 个答案:

答案 0 :(得分:2)

您确定缩进是否正确,左侧分支需要缩进else子句。

我更喜欢在我可以的时候立即返回(我觉得它更容易理解),而不是分配给一个结果,只在函数末尾有一个返回,但这是风格问题。因此,使用不同的注释重写我的首选样式,更正缩进并转换为处理整数列表:

def maxVal(toConsider, avail):
    if toConsider == [] or avail == 0:       # Empty return
        return (0, ())

    nextItem = toConsider[0]
    if nextItem > avail:                     # Too big can only be right
        return maxVal(toConsider[1:], avail)

    # Try with the item
    withVal, withToTake = maxVal(toConsider[1:], avail - nextItem)
    withVal += nextItem
    # Try without the item
    withoutVal, withoutToTake = maxVal(toConsider[1:], avail)

    if withVal > withoutVal:     # See which is best
        return (withVal, withToTake + (nextItem,))
    return (withoutVal, withoutToTake)

>>> maxVal([1,2,3,4,5], 10)
(10, (5, 3, 2))
>>> maxVal([2,2,2,2,2], 7)
(6, (2, 2, 2))

这是一个相当常见的习语,保护条件立即返回,或尝试分解问题。在这种情况下,它会分解以尝试使用值打包列表的其余部分,或尝试打包而不使用该值,并查看哪个返回最佳值。它更喜欢没有列表(withVal > withoutVal)的结果 - 我认为这会给你最小的结果。