Context:FIT1008_MOC, FIT2004_MOC · backbone clustering the measurement foundation — input size, the RAM cost model, time complexity, per-operation cost, and best/worst/average cases
FIT2004 emphasis: distinguish total space from auxiliary space (extra beyond the input — an in-place algorithm uses O(1) auxiliary); and always quote the tightest (Θ) bound available, not just an O upper bound. Input size is often bit-length — for a numberk, n=⌈log2(k+1)⌉, not k itself.
Quick Revision
🎯 Objective: measure the resource (time/space) as a function of input size n ➔ the question is scalability as n→∞.
📦 Core Components:input sizen (often bit-length!) | RAM unit-cost steps | time complexityT(n) | case (best/worst/avg/amortised).
⚡ Key Constraint: unqualified “complexity” = worst case; the RAM unit-cost assumption breaks for variable-size keys (×CompEq).
📝 Core
1. The Resource Question
What it measures ➔ how cost grows with input size — time (elementary ops) or space (peak memory); FIT1008 focuses on time.
Function of n ➔ stated as T(n), not a single number, because we care how it scales.
Time–space trade-off ➔ extra memory buys speed (memoisation, hash tables) and vice-versa.
Bit-length trap ➔ a numberk has size ⌈log2(k+1)⌉≈log2k, notk; halving k to 1 therefore takes Θ(logk) steps, i.e. Θ(bits).
Pseudo-polynomial ➔ a loop running k times is O(2n) in true size (Knapsack O(nW), still NP-hard).
A BOUNDED parameter is a constant ➔ if the spec caps a parameter (n≤106, arr[i]<232), that parameter contributes Θ(1) and vanishes from the bound — the cap makes it independent of input size, not merely small.
Which symbols are free ➔ before quoting a bound, list which parameters can grow without limit; a bound may only be expressed in those. The output size can be one of them ➔ Output-Sensitive Complexity.
3. Running Time & RAM Model
Abstraction ➔ Random-Access Machine — each elementary op = 1 unit, O(1) random access (ignores compiler/machine).
Boundary ➔ breaks for arbitrary-precision arithmetic / external-memory effects.
Declare the unit-cost assumption ➔ ”+ is O(1)” holds only for machine-word operands; on values whose bit-length grows with n an addition costs Θ(bits) ➔ iterative Fibonacci is Θ(n)word operations but Θ(n2)bit operations, since F(n)=Θ(φn) occupies Θ(n) bits (see Fibonacci Sequence, Recursion).
4. Time Complexity T(n) & Step Cost
Counting rules ➔ statement =1 | sequence sums | if = test + branch | loop = body × iters | recursion = recurrence.
CompEq factor ➔ a step is O(1) only for fixed-size keys; length-m strings ⟹ O(nlogn)⋅CompEq.
swap is O(1) ➔ 3 copies; choose a sort minimising the expensive op.
5. Best / Worst / Average Case
Definitions ➔ at fixed n: W=max, B=min, A=ED[cost], with B≤A≤W.
Best ≠ worst only on short-circuit ➔ requires an if/break/early return, else best=worst (selection sort).
Average ≠ amortised ➔ average needs a distribution; amortised is a worst-case-sequence guarantee with no probability.
6. Space: Total vs Auxiliary (FIT2004 quoting standard)
The lecture’s split ➔ space complexity = input space + auxiliary space; the two are reported separately because only the second is the algorithm’s choice.
Total space ➔ input plus everything allocated ⟹ always Ω(n) for an n-element input, so it never discriminates between algorithms.
Auxiliary space ➔ extra beyond the input — the number quoted in a complexity table; in-place≡O(1) auxiliary.
The three iterative reference cases ➔ find_min(arr) scans and keeps one variable ⟹ input Θ(n), auxiliary O(1), in-place · build_list(n) takes a number and allocates an n-slot array ⟹ input O(1), auxiliary Θ(n) · iterative binary_search(arr, target) ⟹ input Θ(n), auxiliary O(1).
Allocating output is auxiliary too ➔ build_list does no recursion and still costs Θ(n) — auxiliary space is any memory beyond the input, not just the call stack.
Recursion stack counts ➔ auxiliary space ≥max live frame chain, i.e. Θ(depth)× frame size — sibling calls run sequentially, so only ONE root-to-leaf path is live at a time (never Θ(total calls)); worked per algorithm in Analysing Recursive Algorithms (Time and Auxiliary Space).
Depth is the discriminator ➔ balanced recursion Θ(logn) frames (Quick Sort with the smaller side recursed first) vs peeling one element Θ(n) frames — the same split that decides time in Divide and Conquer.
Shrinking frames sum, not multiply ➔ frames of size n,n/2,n/4,… total <2n=Θ(n), not Θ(nlogn) — by the r=21 bound in Geometric Series.
Time ≥ auxiliary space — always ➔ memory must be allocated and written before it counts as used, and each cell costs at least one step ⟹ an algorithm quoting Θ(n) auxiliary cannot be o(n) in time. A Θ(logn)-time algorithm claiming Θ(n) auxiliary is a marking error somewhere.
Tightest bound ➔ quote Θ when best = worst; reserve O for a genuine upper-bound-only claim.
⚙️ Core Implementation
🔹 Step-counting & the input-size gotcha
counting rules + bit-length trap
# T(n) counting (RAM model):def bubble_sort(the_list): n = len(the_list) # 2 steps for _ in range(n-1): # outer: n-1 for i in range(n-1): # inner: n-1 if the_list[i] > the_list[i+1]: # 3 steps swap(the_list, i, i+1) # 7 steps# T(n) = 13n^2 - 22n + 12 -> O(n^2)size = k.bit_length() # == ceil(log2(k+1)) ~ log2 k (a NUMBER's size)len([1,2,3,5,8]) # 5 (a collection's size = element count)
💡 Common Mistake:Exact polynomial is accurate but useless ➔ report O(n2); a loop running k times on number k is O(2n) (since n≈log2k), not O(n).
🔹 Cost of a step & best/worst short-circuit
swapO(1) + insertion-sort's best/worst gap
def swap(a, i, j): tmp = a[i]; a[i] = a[j]; a[j] = tmp # always 3 copies => O(1)# Insertion sort inner loop — the source of the best/worst split:while i >= 0 and a[i] > temp: # sorted -> stops at once (BEST, O(n) total) a[i + 1] = a[i] # reverse -> runs k times (WORST, O(n^2)) i -= 1
💡 Common Mistake:Best case ≠ “small input” ➔ both fix size n and differ by arrangement; if comparison costs O(m), multiply every bound by m (O(n2)→O(n2m)).
⚖️ Core Decision Matrix
(Best / Average / Worst time, with the worst-case trigger.)
When It Flips: quote worst for guarantees (real-time/adversarial), average for typical (quicksort, hashing), best rarely. Average ≠ amortised — average assumes a distribution (fails on skewed inputs); amortised is a hard worst-case-sequence guarantee with no probability.
📊 Exam Execution Trace
Manual Execution Trace
Costing code shapes to a closed form:
Step / State
Code Shape
Contribution to T(n)
0 (Init)
simple statement
+1
1
sequence
sum of costs
2
loop ×n
n× body
3
fixed loop ×100
O(1) factor
4
recursive call
a term in a recurrence
Applied Exercise
Problem: Show why an O(nW) Knapsack DP is pseudo-polynomial, not polynomial.
Derivation Proof / Hand-Calculation Walkthrough:
size of capacity WO(nW)=log2W bits⇒W=2log2W=O(n2log2W)=exponential in the encoding length of W
Final Extracted Output: polynomial in the valueW but exponential in its bit-length ⟹ pseudo-polynomial (why Knapsack stays NP-hard).
✍️ Practice
Practice 1: CountBits(x) sets bits=1 then halves x while x>1; CountTotalBits(arr[1..n]) sums CountBits over the array. Give the Θ complexity when (a) ∣arr∣=n, 0≤arr[i]≤2m−1 · (b) same but 1≤n≤106 · (c) ∣arr∣=n, 0≤arr[i]≤232−1.
Hint: First cost one CountBits, then ask which of n and m is actually allowed to grow.
Answer
Inner cost ➔ halving x until x≤1 runs ⌊log2x⌋ times ⟹ Θ(logx), i.e. Θ(bit-length); capped by arr[i]≤2m−1 this is Θ(m) worst case.
(a) Θ(nm) ➔ n calls × Θ(m) each; both parameters are unbounded, so both appear.
(b) Θ(m) ➔ n≤106 is a constant cap ⟹ n=Θ(1) ⟹ it drops out. The loop still runs, but it runs a bounded number of times.
(c) Θ(n) ➔ arr[i]<232 caps the bit-length at 32=Θ(1) ⟹ each CountBits is Θ(1) ⟹ n calls of constant cost.
Why:A cap kills a parameter ➔ asymptotics describe growth, and a quantity that cannot grow contributes a constant factor — which is exactly what Θ discards. This is why real 32/64-bit integer arithmetic is quoted as O(1) while big-integer arithmetic is not.
🧠 Active Recall
Distinguish worst-case, average-case, and amortised complexity, stressing each assumption.
Hint: Each makes a different assumption.
Answer
Short answer:Worst = max, no assumption; average = expectation over a distribution; amortised = worst-case sequence ÷ length, no probability.
Why:Guarantee vs model ➔ amortised O(1) append is a guarantee; average O(1) hash lookup assumes good hashing.
A DP algorithm runs in O(nW) for numeric capacity W — why is it pseudo-polynomial, not polynomial?
Hint: Polynomial means in the bit-length.
Answer
Short answer:W contributes log2W bits ⟹ O(nW)=O(n2log2W) is exponential in the encoding length.
Why:Value vs size ➔ polynomial in the valueW only — hence pseudo-polynomial, and why Knapsack stays NP-hard.
Why is " Θ(n2)" portable but "runs in 3 ms" is not?
Hint: RAM-model machine-independence.
Answer
Short answer:Θ(n2) is a property under the RAM model ⟹ identical step count across machines/compilers.
Why:Folded constants ➔ “3 ms” folds in CPU speed, compiler, and input — none generalise.
Why do selection sort's best and worst cases coincide while quicksort's diverge?
Hint: Short-circuit vs input-dependent pivot.
Answer
Short answer: Selection sort never early-terminates ⟹ cost depends only on n ⟹ best=worst=Θ(n2).
Why:Pivot quality ➔ quicksort’s cost depends on the input (median Θ(nlogn), min/max Θ(n2)), so its cases separate.
Merge Sort is called an O(n)-space sort and Quick Sort an in-place one, yet both allocate. Justify both labels precisely.
Hint: Auxiliary, and count the live stack chain.
Answer
Short answer:Auxiliary space is what is quoted. Merge sort needs a Θ(n) scratch array ⟹ Θ(n); quicksort partitions inside the array, leaving only the recursion stack, O(logn) when the smaller side recurses first.
Why:Live chain, not total calls ➔ sibling calls execute sequentially, so only one root-to-leaf path of frames exists at once; “in-place” means O(1) auxiliary excluding that stack, which is why quicksort’s label survives its O(logn) frames — but degrades to Θ(n) frames on a worst-case pivot.
If each comparison costs O(m) but each swap is O(1), does merge sort or selection sort scale better?
Hint: Weight ops by their true cost.
Answer
Short answer: Merge sort Θ(nmlogn) vs selection sort Θ(n2m) ⟹ merge sort wins.
Why:Comparison-dominated ➔ comparison cost amplified by m dominates, so selection sort’s Θ(n) swap-thrift doesn’t help.