Analysing Recursive Algorithms (Time and Auxiliary Space)

Context: FIT2004_MOC Β· the lecture-2 spine β€” pseudocode βž” recurrence βž” complexity β€” plus the half Solving Recurrences (Telescoping) does not own: reading auxiliary space off the same recursion Parent Framework: Recursion

Quick Revision

  • 🎯 Objective: one recursive function yields two answers βž” solve the recurrence for time, measure the deepest live frame chain for auxiliary space.
  • πŸ“¦ Core Components: read , shrink, work off the code βž” recurrence | telescope βž” time | max depth βž” space.
  • ⚑ Key Constraint: auxiliary space is , never β€” siblings run sequentially, so naive Fibonacci is time but only space.

πŸ“ How It Works

1. Stage 1 β€” code βž” recurrence

  • Read three things βž” = how many recursive calls the body makes Β· the shrink on the argument ( vs ) Β· the non-recursive work per call (, or if the body scans).
  • counts call SITES, not multipliers βž” a scalar on the returned value is arithmetic and folds into ; only a second invocation raises . 2 * f(n//3) ⟹ ; f(n//3) + f(n//3) ⟹ .
  • Write it piecewise with symbolic constants βž” base for , general for , where is the guard threshold read off the if β€” not a reflexive . Without the base line Stage 2 cannot fix the depth.
  • Sum, don’t nest βž” two calls in one body give , an additive branching term β€” not a product. Guards are and fold into .
  • Scope: the argument must SHRINK βž” assessed recurrences decrease the search space each call (n-1, n//2, n//3); recursions whose argument grows are out of scope.

2. Stage 2 β€” recurrence βž” time

  • Owned elsewhere βž” the solving itself, in the mandated Steps 0β†’6b format βž” Solving Recurrences (Telescoping).
  • The one diagnostic βž” subtracting from the argument gives depth ; dividing gives depth .

3. Stage 3 β€” recursion βž” auxiliary space

  • What the stack costs βž” each live call holds a frame (parameters, locals, return address) ⟹ auxiliary space .
  • Depth reuses Stage 2’s number βž” the that solved the recurrence is the depth: shrink-by-one ⟹ frames Β· halving ⟹ frames.
  • Only one root-to-leaf path is live βž” a sibling call cannot start until the previous one returned and popped, so a branching recursion pays for its height, not its node count.
  • Add, don’t max, when a data buffer exists βž” Merge Sort allocates scratch and carries frames ⟹ .
  • An iterative rewrite kills the stack term βž” same time, auxiliary β€” why Binary Search is written with a while loop.

βš™οΈ Core Implementation

πŸ”Ή Reading off the code β€” the multiplier trap

πŸ”Ή Linear vs halving recursion β€” the same shape, two classes

πŸ”Ή Duplicate calls β€” the halving that buys nothing (Applied 2 P4)

  • The leaf-count argument βž” a binary tree of height holds at most nodes ⟹ calls of work, via the exponent swap βž” Solving Recurrences (Telescoping).
  • The transferable repair βž” two recursive calls with identical arguments are a common subexpression β€” bind and reuse. One y = takes from to and to ; eliminating such duplicates across the whole tree is what memoisation turns dynamic programming into when the subproblems overlap.
  • Two independent routes to βž” power_fast squares the result; power_better squares the base. Both make ; mixing them computes .

πŸ”Ή Branching recursion β€” exponential time, linear space

βš–οΈ Complexity

(Every function in the lecture-2 deck, both deliverables side by side.)

FunctionRecurrenceTime (worst)Auxiliary spaceWhat sets the space
powerone frame per decrement
power_betterhalving depth
power_naive (duplicate call)binary tree of height ⟹ nodes
power_fast (y = bound once) cut from to
recursive Linear Searchone frame per element
Merge Sortscratch array dominates the stack
naive fibonacciheight, not node count

When It Flips: the two columns decouple whenever the recursion branches. With one call per body, time and space share the depth; with two or more, time counts nodes and space counts height, and the gap can be exponential.

πŸ“Š Exam Execution Trace

Manual Execution Trace

power_better(2, 13) β€” the live stack, deepest frame at the bottom:

StepFrame entered / poppedxnStack depthReturns
0 (Init)push pb(2, 13)2131odd βž” defer
1push pb(4, 6)462even βž” defer
2push pb(16, 3)1633odd βž” defer
3push pb(256, 1)25614 (max)base βž”
4pop to depth 31633
5pop to depth 2462
6pop to depth 12131

Read-off: frames ⟹ auxiliary; multiplications ⟹ time; βœ“.

Applied Exercise

Problem: linear_search_recursive(array[1..N], target, index=1) returns False if index > N, the index on a match, else recurses with index+1. Give worst-case time and auxiliary space, and say what the iterative version changes.

Final Extracted Output: time, auxiliary. The iterative scan keeps time but drops to auxiliary β€” the recursion buys nothing, which is why Linear Search is a loop.

⚠️ Common Mistakes

  • πŸ’‘ β€œIn-place” does not survive recursion silently βž” a recursive algorithm that allocates nothing still pays for frames; quote it or lose the space mark.
  • πŸ’‘ Quoting space for Merge Sort βž” scratch arrays at different levels are not simultaneously live; sum to by the bound in Geometric Series.
  • πŸ’‘ Forgetting the base case in Stage 1 βž” the general form in is unsolvable without it.

πŸ”­ Beyond the lecture (not in the slides) β€” CPython performs no tail-call elimination, so even a tail-recursive linear_search_recursive really does hold frames and raises RecursionError near depth .

🧠 Active Recall