I'm having some difficulty in understanding recursion, because for some cases I think it really make sense but in other cases I find it hard to comprehend. I know recursion can help to break down the problem into sub problems which would be easier to solve, and then the solutions to these sub problems can be combined to get the main solution to the main problem we're trying to solve. For instance, we have code to find the Fibonnaci sum of n. Of course, this is not the fastest implementation because it results in many recalculations.
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)
In this case, I understand what's happening because the returned value is stored and reused in the subsequent else statement after solving for the base case, that is, fib(1)+fib(0) will return 2 for the case of fib(2), then the result for fib(2) is used in the calculation of fib(3) where fib(3) =fib(2)+fib(1), for which we already have answers. This is clear to me because the result that is being returned after the last base case (i.e. recursive calls higher up than the base case) is being reused, with the ultimate goal to get the answer.
However, in certain cases, I find that recursion is not so straightforward, and this gets me really confused. For example, we have this code:
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
What I don't get is where does the returned result get ever used in the subsequent recursive call after the base case is being called? It seems that the result of the recursive calls below are never being connected with other recursive calls - is this true? That is unlike the case of the Fibonacci recursion seen above. For example, once I reached the base case of having toConsider=[] or avail==0, my result will be (0,()), and then I return this result. But how is this result from the last base case going to be used in the second last, penultimate recursion? Deducing further, then it seems like the third last recursion will have nothing to do with the second last, and fourth last recursion nothing to do with the third last, and so on...until the main solution. But of course this is obviously not the case. I understand how the code works in the superficial sense, meaning it describes exactly what is being done in a decision tree from top down, to the last base case (or the leaf of the nodes), and it make sense if we get the answer anyway. But how does the result in each recursion get stored, so that the final result will reflect that the answers done in the many recursions that have taken place?
Also, is there multiple understanding or perspectives to understand recursion? The Fibonacci example may provide an instance where recursion is seen from coming from bottom up to solve a problem, but the decision tree one instead offers the perspective of seeing things from top down. Meaning that we go down the decision tree, get every answer until the last base case answer for which we already know a solution, and then we sum up all these answers to get the final answer which we want. Is this correct? So is this two main ways of understanding recursion - from bottom up or top down?
The above codes come from an introductory computer science book by MIT. I am currently learning computer science independently, and I really hope you guys can help me out. Thank you! :D
withVal, withToTake = maxVal(...)andwithoutVal, withoutToTake = maxVal(...). The results of the calls are unpacked into the variables on the left side of the assignment. Later code uses them to build the new return value. - Blckknght