← Math|76 of 100
Discrete Mathematics

Recurrence Relations

Solve linear recurrences, characteristic equations, Master theorem, and analyze algorithm complexity.

📂 Recurrences📖 Lesson 76 of 100🎓 Free Course

Advertisement

Recurrence Relations

â„šī¸ Why It Matters

Recurrence relations describe sequences where each term depends on previous terms. They are essential for analyzing recursive algorithms (merge sort, quicksort), counting combinatorial structures, modeling population growth, and solving dynamic programming problems. The Master theorem provides instant complexity results for divide-and-conquer algorithms. Understanding recurrences is fundamental to algorithm analysis and discrete mathematics.


Definitions

DfRecurrence Relation

A recurrence relation defines a sequence {an}\{a_n\} where each term is expressed as a function of preceding terms: an=f(an−1,an−2,â€Ļ,an−k)a_n = f(a_{n-1}, a_{n-2}, \ldots, a_{n-k}) for nâ‰Ĩkn \geq k, with initial conditions a0,a1,â€Ļ,ak−1a_0, a_1, \ldots, a_{k-1}.

DfLinear Homogeneous Recurrence

A linear homogeneous recurrence with constant coefficients has the form an=c1an−1+c2an−2+⋯+ckan−ka_n = c_1 a_{n-1} + c_2 a_{n-2} + \cdots + c_k a_{n-k} where all cic_i are constants and no f(n)f(n) term appears.

DfLinear Non-Homogeneous Recurrence

A linear non-homogeneous recurrence adds a function of nn: an=c1an−1+c2an−2+⋯+f(n)a_n = c_1 a_{n-1} + c_2 a_{n-2} + \cdots + f(n).

DfCharacteristic Equation

For the recurrence an=c1an−1+c2an−2a_n = c_1 a_{n-1} + c_2 a_{n-2}, the characteristic equation is r2−c1r−c2=0r^2 - c_1 r - c_2 = 0. Its roots determine the general solution.

DfDivide-and-Conquer Recurrence

A recurrence of the form T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n) where aâ‰Ĩ1a \geq 1, b>1b > 1, and f(n)f(n) describes the work done outside recursive calls.


Formulas

Second-Order Characteristic Equation

r2−c1r−c2=0r^2 - c_1 r - c_2 = 0

Here,

  • rr=Root of the characteristic equation
  • c1,c2c_1, c_2=Coefficients from the recurrence

General Solution (Distinct Roots)

an=Ar1n+Br2na_n = A r_1^n + B r_2^n

Here,

  • r1,r2r_1, r_2=Distinct roots of the characteristic equation
  • A,BA, B=Constants determined by initial conditions

General Solution (Repeated Root)

an=(A+Bn)rna_n = (A + Bn) r^n

Here,

  • rr=Repeated root of multiplicity 2
  • A,BA, B=Constants determined by initial conditions

Fibonacci Closed Form (Binet's Formula)

Fn=Ī•nâˆ’Īˆn5,Ī•=1+52,Έ=1−52F_n = \frac{\phi^n - \psi^n}{\sqrt{5}}, \quad \phi = \frac{1+\sqrt{5}}{2}, \quad \psi = \frac{1-\sqrt{5}}{2}

Here,

  • Ī•\phi=Golden ratio
  • Έ\psi=Conjugate of the golden ratio

Solving Linear Homogeneous Recurrences

Step-by-Step Method

  1. Write the characteristic equation from the recurrence
  2. Solve the characteristic equation for roots r1,r2,â€Ļr_1, r_2, \ldots
  3. If roots are distinct: an=A1r1n+A2r2n+⋯a_n = A_1 r_1^n + A_2 r_2^n + \cdots
  4. If a root rr has multiplicity mm: include terms (A1+A2n+⋯+Amnm−1)rn(A_1 + A_2 n + \cdots + A_m n^{m-1}) r^n
  5. Use initial conditions to solve for constants AiA_i

Example: Fibonacci Sequence

The Fibonacci recurrence Fn=Fn−1+Fn−2F_n = F_{n-1} + F_{n-2} with F0=0,F1=1F_0 = 0, F_1 = 1:

Characteristic equation: r2−r−1=0r^2 - r - 1 = 0

Roots: r1=Ī•=1+52≈1.618r_1 = \phi = \frac{1+\sqrt{5}}{2} \approx 1.618, r2=Έ=1−52≈−0.618r_2 = \psi = \frac{1-\sqrt{5}}{2} \approx -0.618

General solution: Fn=AĪ•n+BΈnF_n = A \phi^n + B \psi^n

Using initial conditions: A=15A = \frac{1}{\sqrt{5}}, B=−15B = -\frac{1}{\sqrt{5}}

Closed form: Fn=Ī•nâˆ’Īˆn5F_n = \frac{\phi^n - \psi^n}{\sqrt{5}}


Python Implementations

Solving Linear Recurrences

import numpy as np
from functools import lru_cache

def solve_linear_recurrence(c1, c2, a0, a1, n):
    """Solve a_n = c1*a_{n-1} + c2*a_{n-2}."""
    coeffs = [1, -c1, -c2]
    roots = np.roots(coeffs)

    if len(set(np.round(roots, 10))) == 2:
        r1, r2 = roots
        A = (a1 - r2 * a0) / (r1 - r2)
        B = a0 - A
        return A * r1**n + B * r2**n
    else:
        r = roots[0]
        return (a0 + n * (a1 / r - a0)) * r**n

# Fibonacci
for i in range(11):
    print(f"F({i}) = {solve_linear_recurrence(1, 1, 0, 1, i):.0f}")

Iterative Solution

def linear_recurrence_iterative(c1, c2, a0, a1, n):
    """Compute a_n iteratively."""
    if n == 0:
        return a0
    if n == 1:
        return a1
    prev2, prev1 = a0, a1
    for _ in range(2, n + 1):
        curr = c1 * prev1 + c2 * prev2
        prev2, prev1 = prev1, curr
    return prev1

# Verify
for n in range(11):
    print(f"F({n}) = {linear_recurrence_iterative(1, 1, 0, 1, n)}")

Memoized Recursion

@lru_cache(maxsize=None)
def fibonacci_memo(n):
    if n <= 1:
        return n
    return fibonacci_memo(n - 1) + fibonacci_memo(n - 2)

for i in range(20):
    print(f"F({i}) = {fibonacci_memo(i)}", end="  ")

Master Theorem

ThMaster Theorem

For a divide-and-conquer recurrence T(n)=aT(n/b)+O(nd)T(n) = aT(n/b) + O(n^d) where aâ‰Ĩ1a \geq 1, b>1b > 1, dâ‰Ĩ0d \geq 0:

  • Case 1: If d<log⁥bad < \log_b a, then T(n)=O(nlog⁥ba)T(n) = O(n^{\log_b a})
  • Case 2: If d=log⁥bad = \log_b a, then T(n)=O(ndlog⁥n)T(n) = O(n^d \log n)
  • Case 3: If d>log⁥bad > \log_b a, then T(n)=O(nd)T(n) = O(n^d)

Master Theorem Applications

AlgorithmRecurrenceaabbddComplexity
Binary SearchT(n)=T(n/2)+O(1)T(n) = T(n/2) + O(1)120O(log⁥n)O(\log n)
Merge SortT(n)=2T(n/2)+O(n)T(n) = 2T(n/2) + O(n)221O(nlog⁥n)O(n \log n)
StrassenT(n)=7T(n/2)+O(n2)T(n) = 7T(n/2) + O(n^2)722O(n2.81)O(n^{2.81})
KaratsubaT(n)=3T(n/2)+O(n)T(n) = 3T(n/2) + O(n)321O(n1.58)O(n^{1.58})
import math

def master_theorem(a, b, d):
    log_b_a = math.log(a) / math.log(b)
    if d < log_b_a:
        return f"O(n^{log_b_a:.2f})"
    elif abs(d - log_b_a) < 1e-9:
        return f"O(n^{d} log n)"
    else:
        return f"O(n^{d})"

print(master_theorem(2, 2, 1))  # O(n^1.00 log n) - Merge Sort
print(master_theorem(1, 2, 0))  # O(n^0.00 log n) = O(log n) - Binary Search
print(master_theorem(7, 2, 2))  # O(n^2.81) - Strassen

Non-Homogeneous Recurrences

â„šī¸ Solving Non-Homogeneous Recurrences

For an=c1an−1+f(n)a_n = c_1 a_{n-1} + f(n):

  1. Solve the homogeneous part: an(h)=c1an−1a_n^{(h)} = c_1 a_{n-1}
  2. Find a particular solution an(p)a_n^{(p)} based on f(n)f(n):
    • If f(n)=kf(n) = k: try an(p)=Ca_n^{(p)} = C (constant)
    • If f(n)=knf(n) = kn: try an(p)=Cn+Da_n^{(p)} = Cn + D
    • If f(n)=k⋅bnf(n) = k \cdot b^n and b≠c1b \neq c_1: try an(p)=Cbna_n^{(p)} = Cb^n
  3. General solution: an=an(h)+an(p)a_n = a_n^{(h)} + a_n^{(p)}
  4. Use initial conditions to find constants

Applications in AI/ML

â„šī¸ Applications in AI and Machine Learning

  • Algorithm Complexity Analysis: Master theorem directly gives Big-O for recursive algorithms
  • Dynamic Programming: DP state transitions are recurrences; solving them gives optimal substructure
  • Population Models: Recurrence relations model population dynamics, predator-prey systems
  • Signal Processing: Linear recurrences implement digital filters (FIR, IIR)
  • Time Series Forecasting: ARIMA models use autoregressive recurrences
  • Fractal Generation: Recurrences define fractal dimensions and recursive structures
  • Neural Architecture Search: Tree-structured NAS uses recurrences to count architectures

Common Mistakes

MistakeCorrection
Forgetting initial conditionsThe general solution has undetermined constants; use a0,a1a_0, a_1 to solve them
Applying Master theorem to non-divide-and-conquerMaster theorem requires T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n) form exactly
Mixing up cases in Master theoremCompare dd with log⁥ba\log_b a, not with aa or bb individually
Ignoring repeated rootsRepeated roots require polynomial factors: (A+Bn)rn(A + Bn)r^n not just ArnAr^n
Assuming closed form always existsNot all recurrences have nice closed forms
Using Master theorem on T(n)=T(n−1)+nT(n) = T(n-1) + nThis is not divide-and-conquer; solve by iteration or substitution
Forgetting to verify solution against base casesAlways check that your closed form produces the correct initial values
Confusing homogeneous and non-homogeneousNon-homogeneous has an extra f(n)f(n) term; treat separately

Interview Questions

  1. What is the Master theorem and when does it apply? It solves T(n)=aT(n/b)+O(nd)T(n) = aT(n/b) + O(n^d) in three cases based on comparing dd with log⁥ba\log_b a. It applies only to divide-and-conquer recurrences.

  2. How do you solve the Fibonacci recurrence? Characteristic equation r2−r−1=0r^2 - r - 1 = 0 gives roots Ī•\phi and Έ\psi. Closed form: Fn=Ī•nâˆ’Īˆn5F_n = \frac{\phi^n - \psi^n}{\sqrt{5}}.

  3. What is the time complexity of merge sort and why? O(nlog⁥n)O(n \log n). The recurrence T(n)=2T(n/2)+nT(n) = 2T(n/2) + n satisfies Case 2 of Master theorem with a=2,b=2,d=1a=2, b=2, d=1.

  4. How do you solve a recurrence with repeated roots? If root rr has multiplicity 2, the solution is (A+Bn)rn(A + Bn)r^n instead of just ArnAr^n.

  5. What is the difference between linear and non-linear recurrences? Linear recurrences express ana_n as a linear combination of previous terms. Non-linear recurrences involve products, powers, or other non-linear functions of previous terms.

  6. How do you analyze recursive algorithms without Master theorem? Use the recursion tree method: sum work at each level, or use substitution method (guess and verify by induction).

  7. What is the Akra-Bazzi method? Generalizes Master theorem for recurrences like T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n) where f(n)f(n) can be any polynomial.

  8. When does the Master theorem not apply? When the recurrence is not divide-and-conquer (e.g., T(n)=T(n−1)+1T(n) = T(n-1) + 1), or when subproblems have unequal sizes.


Practice Problems

📝Practice: Master Theorem

Problem: Solve T(n)=2T(n/2)+nT(n) = 2T(n/2) + n, T(1)=1T(1) = 1.

💡Solution: Master Theorem

a=2,b=2,d=1a = 2, b = 2, d = 1. log⁥ba=log⁥22=1=d\log_b a = \log_2 2 = 1 = d. Case 2 applies: T(n)=O(nlog⁥n)T(n) = O(n \log n).

📝Practice: Characteristic Equation

Problem: Solve an=5an−1−6an−2a_n = 5a_{n-1} - 6a_{n-2} with a0=1,a1=4a_0 = 1, a_1 = 4.

💡Solution: Characteristic Equation

Characteristic equation: r2−5r+6=0r^2 - 5r + 6 = 0. Roots: r=2,3r = 2, 3. Solution: an=A⋅2n+B⋅3na_n = A \cdot 2^n + B \cdot 3^n. From a0=1a_0 = 1: A+B=1A + B = 1. From a1=4a_1 = 4: 2A+3B=42A + 3B = 4. Solving: A=−1,B=2A = -1, B = 2. Thus an=−2n+2⋅3na_n = -2^n + 2 \cdot 3^n.

📝Practice: Fibonacci

Problem: Find F10F_{10} using the closed form.

💡Solution: Fibonacci

Ī•=1.618034,Έ=−0.618034\phi = 1.618034, \psi = -0.618034. F10=Ī•10âˆ’Īˆ105=122.99−0.008132.236=55F_{10} = \frac{\phi^{10} - \psi^{10}}{\sqrt{5}} = \frac{122.99 - 0.00813}{2.236} = 55.

📝Practice: Tower of Hanoi

Problem: The Tower of Hanoi satisfies T(n)=2T(n−1)+1T(n) = 2T(n-1) + 1. Find the closed form.

💡Solution: Tower of Hanoi

T(n)=2T(n−1)+1T(n) = 2T(n-1) + 1. Unrolling: T(n)=2kT(n−k)+2k−1T(n) = 2^k T(n-k) + 2^k - 1. For k=nk = n: T(n)=2nT(0)+2n−1=2n+1−1T(n) = 2^n T(0) + 2^n - 1 = 2^{n+1} - 1.


Recursion Tree Method

â„šī¸ Recursion Tree Method

When the Master theorem does not apply, use the recursion tree method:

  1. Draw the tree of recursive calls
  2. Compute the work done at each level
  3. Sum across all levels to get the total complexity

For T(n)=2T(n/2)+nT(n) = 2T(n/2) + n: root does nn work, two children each do n/2n/2, four grandchildren each do n/4n/4, etc. Each level does nn total work, and there are log⁥2n\log_2 n levels. Total: O(nlog⁥n)O(n \log n).

def recursion_tree_depth(T_func, n, depth=0):
    """Visualize recursion tree depth."""
    if n <= 1:
        return depth
    return T_func(n, depth)

def sum_of_geometric_series(a, r, n):
    """Sum of geometric series: a + ar + ar^2 + ... + ar^(n-1)."""
    if abs(r) < 1:
        return a * (1 - r**n) / (1 - r)
    return a * (r**n - 1) / (r - 1)

# Example: T(n) = 2T(n/2) + n has log2(n) levels, each doing n work
import math
for n in [8, 16, 32, 64]:
    levels = int(math.log2(n))
    total = sum(n // (2**i) * 2**i for i in range(levels + 1))
    print(f"T({n}): {levels} levels, total work ~ {total}, O(n log n) = {n * levels}")

Substitution Method

â„šī¸ Substitution Method

The substitution method works by guessing the form of the solution and using induction to find constants:

  1. Guess the asymptotic bound (e.g., T(n)=O(n2)T(n) = O(n^2))
  2. Assume it holds for all values less than nn
  3. Substitute into the recurrence and solve for constants
  4. Verify the guess satisfies the recurrence

This method is powerful but requires a good initial guess, often guided by the recursion tree.

def substitution_verify(c1, c2, bound_func, n_values):
    """Verify a guessed bound satisfies the recurrence."""
    for n in n_values:
        if n <= 1:
            continue
        lhs = c1 * bound_func(n)
        rhs = c2 * bound_func(n // 2) + n
        if lhs < rhs:
            return False, n
    return True, None

import math
result, failing_n = substitution_verify(1, 2, lambda n: n * math.log2(n), [4, 8, 16, 32])
print(f"Bound O(n log n) verified: {result}")

Quick Reference

RecurrenceSolutionComplexityMethod
T(n)=T(n−1)+1T(n) = T(n-1) + 1nnO(n)O(n)Iteration
T(n)=T(n−1)+nT(n) = T(n-1) + nn(n+1)/2n(n+1)/2O(n2)O(n^2)Iteration
T(n)=2T(n−1)+1T(n) = 2T(n-1) + 12n+1−12^{n+1} - 1O(2n)O(2^n)Iteration
T(n)=T(n/2)+1T(n) = T(n/2) + 1log⁥2n\log_2 nO(log⁥n)O(\log n)Master Case 2
T(n)=2T(n/2)+nT(n) = 2T(n/2) + nnlog⁥2nn \log_2 nO(nlog⁥n)O(n \log n)Master Case 2
T(n)=2T(n/2)+1T(n) = 2T(n/2) + 12n−12n - 1O(n)O(n)Master Case 1
T(n)=7T(n/2)+n2T(n) = 7T(n/2) + n^2O(n2.81)O(n^{2.81})O(nlog⁥27)O(n^{\log_2 7})Master Case 1
Fn=Fn−1+Fn−2F_n = F_{n-1} + F_{n-2}Ī•nâˆ’Īˆn5\frac{\phi^n - \psi^n}{\sqrt{5}}O(Ī•n)O(\phi^n)Characteristic
an=c1an−1+c2an−2a_n = c_1 a_{n-1} + c_2 a_{n-2}Ar1n+Br2nAr_1^n + Br_2^nVariesCharacteristic

Cross-References

Lesson Progress76 / 100