Recursion

Context: FIT1008_MOC, FIT2102_MOC · defining an Algorithm in terms of itself · backbone clustering Notation, Accumulator, Auxiliary Function, vs-Iteration, →Iteration-via-Stack, and — from FIT2102 — recursion as the declarative replacement for the loop

Quick Revision

  • 🎯 Objective: reduce to smaller subproblems of the same kind until a base case ➔ cost via recurrence, correctness via induction.
  • 📦 Core Components: base + call + convergence + combine | classified by count/route/tail | reshaped by accumulator/auxiliary function.
  • ⚡ Key Constraint: stack frames (no TCO in Python, none in Chrome’s V8) ➔ overflow; removed by an accumulator (forward) or explicit Stack (ADT) (backward).

📝 Core

1. Recursion (Four Components)

  • Core mechanism ➔ a function calls itself on strictly smaller inputs.
  • Four partsbase case(s) + recursive call(s) + convergence + combination.
  • Cost / correctness ➔ cost = recurrence (unroll/recursion tree); correctness = structural induction; no base ⟹ RecursionError.

2. Notation (Classifying)

  • Count ➔ unary / binary / -ary (the branching factor of the call tree).
  • Route ➔ direct vs indirect/mutual.
  • Tail ➔ recursive call’s result is the result (nothing on the way back) ➔ binary ⟹ unless subproblems shrink fast or memoised.

3. Accumulator (Forward-Carry → Tail)

  • Mechanism ➔ extra parameter carries the partial result forward, combining on the way in.
  • Effect ➔ makes the recursion tail-recursive, kills recomputation (Fibonacci ).
  • Boundary ➔ only when result builds forward; memoisation/DP is the general alternative ( space).

4. Auxiliary Function (Driver + Worker)

  • Pattern ➔ private worker carries a converging argument the public type can’t shrink (a LinkList can’t recurse on self).
  • Parameter jobsconverge (current.link) | accumulate | carry context (item, lo/hi).
  • or short-circuit ➔ a free second base case; plumbing hidden behind a clean public signature.

5. Recursion vs Iteration

  • Equivalence ➔ loops vs self-calls are Turing-equivalent; difference = where state lives.
  • Practical cap ➔ TCO-less languages ⟹ iteration needed for large .
  • Conversion seed ➔ the base case is the negation of the loop’s continuation guard.

6. →Iteration via Explicit Stack

  • Mechanism ➔ simulate the run-time stack with an explicit Stack (ADT) — push pending work, pop in a loop.
  • When ➔ general conversion when an accumulator can’t (work builds on the way back).
  • Payoff ➔ same space but on the heap ➔ no fixed call-stack limit.

7. Recursion as the Loop Replacement (FIT2102)

  • Direction of use is reversed ➔ FIT1008 converts recursion to iteration for safety; FIT2102 converts iteration to recursion because a loop needs a mutable index and a mutable accumulator, and mutation is what the declarative style removes.
  • What the accumulator replaces ➔ the let that the loop body updated. Carrying it as a parameter means every binding in sight can be const, and the function becomes pure — same arguments, same result, no state outside its own frame.
  • Prepend vs append is the whole trick ➔ an accumulator built by prepending (digit + acc) emerges in the original left-to-right order even though the recursion peels the last piece first; appending silently reverses it. This is where the marks go on any digit/character-building recursion.
  • Recursion vs the Array HOFsmap/filter/reduce cover the traversals that visit each element once ➔ JavaScript Functions as Values. Reach for explicit recursion when the shrinking argument is not a collection (a number, a string being consumed, a cons list) or when you must stop early.
  • Failure mode is unchanged ➔ JavaScript specifies proper tail calls but V8 does not implement them, so the tail form is conceptually and frames in Chrome. The purity argument survives; the stack-safety argument does not.

⚙️ Core Implementation

🔹 Basic vs Accumulator (factorial / Fibonacci)

🔹 JavaScript: killing a loop with an accumulator (FIT2102)

🔹 Explicit-Stack de-recursification (power)

⚖️ Core Decision Matrix

Recursion shapeConvert viaRecurrence → costExample
Tail / forwardaccumulator → plain loop (no stack)factorial, fib_aux
Non-tail, single callone explicit stack of argumentspower, binary search
Multiple calls (balanced)explicit stack of work itemsMerge Sort
Multiple calls (overlapping)accumulator or memoisation/DPnaive Fibonacci

When It Flips: same time Big-O, but recursion uses space vs iteration's — TCO would erase it (Python/Java lack it). The equivalence is to general iteration; the primitive-recursive / bounded-for fragment can't express Ackermann.

📊 Exam Execution Trace

Manual Execution Trace

power_iter(2, 5) (so ):

Step / StateTrigger Opstntmp Payload
0 (Init)push[5]2
1push[5,2]1
2push[5,2,1]01
3pop 1 (odd)[5,2]
4pop 2 (even)[5]
5pop 5 (odd)[]

Applied Exercise

Problem: Unroll the factorial recurrence to its closed-form cost. Derivation Proof / Hand-Calculation Walkthrough:

Final Extracted Output: unary recursion ⟹ a path of frames ⟹ time and stack space.

🧠 Active Recall