Analysing Recursive Algorithms (Time and Auxiliary Space)

Context: FIT2004_MOC Β· the Week 1 lecture-2 spine β€” pseudocode βž” recurrence βž” complexity β€” and 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 ( constant, 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) ⟹ β€” worked below.
  • 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 βž” if N == 0 / if index > N fold into ; they never change the growth class.
  • Scope: the argument must SHRINK βž” assessed recurrences decrease the search space each call (n-1, n//2, n//3); recursions whose argument grows in value are out of assessment scope.

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

  • Owned elsewhere βž” the solving itself, in the mandated Steps 0β†’6b exam format (levels βž” substitute βž” general form βž” base βž” closed form βž” complexity βž” verify) βž” 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 has 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 ⟹ ; the lecture writes both terms, the tightest quote collapses them.
  • An iterative rewrite kills the stack term βž” same recurrence-derived time, auxiliary β€” the reason 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, stated as the sheet does βž” the call tree is binary of height ; a binary tree of height (root at level ) holds at most nodes ⟹ calls of work each. The collapse uses the exponent swap βž” Solving Recurrences (Telescoping).
  • The transferable repair βž” two recursive calls with identical arguments are a common subexpression β€” bind the result to a variable and reuse it. One y = deletes an entire branch of the tree, taking from to and to .
  • It is the seed of memoisation βž” eliminating duplicate calls within one frame is the trivial case; eliminating them across the whole tree is what dynamic programming does when the subproblems overlap.
  • Two independent routes to βž” power_fast keeps the base and squares the result; power_better above squares the base and passes x*x. Both make ; neither is better, and mixing them (squaring both) 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 here, which is why the Linear Search reference implementation 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 βž” the scratch arrays at different levels are not simultaneously live; sizes sum to by the bound in Geometric Series.
  • πŸ’‘ Forgetting the base case in Stage 1 βž” the general form in is unsolvable without it; a recurrence written as alone earns no marks.

πŸ”­ 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 . The space cost is a runtime fact here, not just an accounting convention.

🧠 Active Recall