Recursion#

Introduction#

Recussion generally follows Divide-Conquer algorithm:

  • Divide the problem into a number of subproblems that are smaller instances of the same problem.

  • Conquer the subproblems by solving them recursively. If the subproblem sizes are small enough, however, just solve the subproblems in a straightforward manner.

  • Combine the solutions to the subproblems into the solution for the original problem

Some Cases#

Maximum/Minimum#

def maximum_recursion(A, i):
    if i < 1: 
        return A[0]
    else:
        return max(maximum_recursion(A, i-1),A[i])

A = [2,4,5,9,1]
print(maximum_recursion(A,len(A)-1))

A = [1, 1, 1, 1]
print(maximum_recursion(A,len(A)-1))
9
1
def reverse(s):
    """Return a string in reverse order"""
    res = ''
    n = len(s)

    if n <= 1: # base case
        res = s
    else:
        r = reverse(s[:n-1])
        res = s[n-1]+r
    return res

print(reverse("hello world"))
dlrow olleh

Fibonacci Number#

# using recurrence - duplicately recalculate f(n-2) - bad recurrence using binary recursion - O(2^n)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(10))
55
# using recurrence - good recurrence using linear recursion - O(n)
def fibonacci(n):
    if n <= 1:
        return (n, 0)
    else:
        (a, b) = fibonacci(n-1)
        return (a+b, a)

print(fibonacci(100))
(354224848179261915075, 218922995834555169026)
# using memoized recurrence - top-down dynamic programming O(n)
def fibonacci(n, r = {}):
    if n in r:
        return r[n]
    elif n <= 1:
        r[n] = n
    else:
        r[n] = fibonacci(n-1, r) + fibonacci(n-2, r)

    return r[n]

print(fibonacci(100))
354224848179261915075
# using bottom-up dynamic programming O(n)
def fibonacci(n):
    r = [0.]*(n+1)
    for j in range(n+1):
        if j <= 1:
            r[j] = j
        else:
            r[j] = r[j-1] + r[j-2]
    return r[n]

print(fibonacci(100))
354224848179261915075