Trees
Time limit: 90 minutes Total marks: 60 Instructions: Answer all three questions. Show all reasoning, invariants, and complexity derivations. Pseudocode may be in any clear imperative style. Partial credit is given for correct invariants even if final code is incomplete.
Question 1 — Heaps, Heapify and an Amortized Argument (20 marks)
A max-heap is stored as a 0-indexed array where node has children and .
(a) Prove that the bottom-up (Floyd) buildHeap runs in time. Set up the exact cost sum
and show it is bounded by a constant times . You must evaluate the closed form of from first principles (do not just quote it). (7)
(b) Given the array A = [3, 9, 2, 1, 4, 5], run bottom-up heapify to build a max-heap. Show the array after each siftDown call. Give the final array. (5)
(c) Heap sort performs buildHeap then extractMax operations. State and justify the exact asymptotic bound , and prove heap sort is not stable by giving a minimal concrete counterexample (input array + resulting order). (4)
(d) A physicist models a discrete energy system where the min-heap always exposes the lowest-energy microstate. If decrease-key is used to lower one element's key by moving it from a leaf toward the root, prove its worst-case cost is by bounding the number of swaps by the tree height. (4)
Question 2 — Balanced BSTs: AVL Proof and Construction (20 marks)
(a) Let be the minimum number of nodes in an AVL tree of height . Derive the recurrence with base cases, and prove by induction that where is the -th Fibonacci number. Use this to prove an AVL tree of nodes has height ; state the constant (base of the log). (8)
(b) Insert the following keys in order into an initially empty AVL tree, drawing the tree after each rotation and naming the rotation type (LL, RR, LR, RL): Give the final balance factor of every node. (8)
(c) Explain precisely why a Red-Black tree guarantees height , referencing the black-height invariant and the no-two-consecutive-reds rule. Contrast this bound with the AVL bound from part (a) in one sentence: which is more strictly balanced and why does that matter for insert-heavy vs lookup-heavy workloads? (4)
Question 3 — Range Structures: Fenwick vs Segment Tree (20 marks)
Consider the array A = [2, 1, 5, 3, 4, 6] (1-indexed, ).
(a) Build a Fenwick tree (BIT) for prefix sums. Show the value stored at each index , explaining tree[i] = sum over range (i - lowbit(i), i]. Then compute prefixSum(5) by tracing the index descent . (6)
(b) Perform update(3, +2) (add 2 to ). List every tree index touched via the ascent and give the new tree array. Verify by recomputing prefixSum(6) before and after. (5)
(c) Prove that both update and prefixSum in a Fenwick tree are by bounding the number of iterations by the number of set/cleared bits in the traversal, which is . (4)
(d) A segment tree supports range-min queries. Derive that a segment tree over leaves has at most nodes when stored in an array with children of node at . Prove this bound by considering the height and the fact the last level may be nearly full. (5)
Answer keyMark scheme & solutions
Question 1
(a) [7 marks]
- A node at height costs for
siftDown(at most swaps down). (1) - Number of nodes at height is at most . (1)
- Total: . (1)
- Evaluate from first principles: Start with for . Differentiate: . Multiply by : . (2)
- At : . (1)
- Hence . (1)
(b) [5 marks] Max-heap, 0-indexed, A=[3,9,2,1,4,5], . Last internal node index . Sift from down to .
- (val 2, children idx5=5): swap →
[3,9,5,1,4,2]. (1) - (val 9, children idx3=1,idx4=4): 9 largest, no change →
[3,9,5,1,4,2]. (1) - (val 3, children idx1=9,idx2=5): swap with 9 →
[9,3,5,1,4,2]; now 3 at idx1, children idx3=1,idx4=4: swap with 4 →[9,4,5,1,3,2]. (2) - Final:
[9,4,5,1,3,2]. (1)
(c) [4 marks]
buildHeap= ; each ofextractMax= (siftDown from root). Total . (2)- Not stable: input
[(2,a),(2,b)](keys equal, tags distinguish). Building max-heap and extracting swaps them:extractMaxmoves last element to root and the relative order of equal keys is not preserved. Minimal counterexample:[2a, 2b, 1]→ heap sort ascending output can yield1, 2b, 2a, reversing equal keys. (2)
(d) [4 marks]
- In min-heap, decreasing a key may violate parent ≤ child upward, so we siftUp. (1)
- Each siftUp step moves the element one level toward root and does 1 comparison + possible swap. (1)
- The number of levels is the height . (1)
- Therefore worst case swaps . (1)
Question 2
(a) [8 marks]
- Minimum-node AVL of height : root plus two subtrees, one of height (to reach height ) and, to minimize nodes while keeping balance factor within , the other of height . So . (2)
- Base: , . (1)
- Claim where . Base: ? — use : ✓; ✓. (1)
- Induction: assume for . Then . ✓ (2)
- Since (roughly), , so ; base . (1)
(b) [8 marks]
Insert 10, 20: tree 10 → right 20. Insert 30: right-right chain at 10, BF(10) = -2 → RR rotation at 10, 20 becomes root:
20
/ \
10 30
(2) Insert 25: goes right of 20, left of 30. BFs fine.
20
/ \
10 30
/
25
(1) Insert 5: left of 10. Balanced.
20
/ \
10 30
/ /
5 25
(1) Insert 3: left of 5. Now node 10 has left subtree height 2, right 0 → BF(10)=+2, inserted in left-left → LL rotation at 10, 5 becomes local root:
20
/ \
5 30
/ \ /
3 10 25
(2) Final balance factors (left height − right height):
- 20: left subtree(5) height 2, right subtree(30) height 2 → 0
- 5: children 3,10 both height 1 → 0
- 30: left 25 (h1), right none → +1
- 3: leaf 0, 10: leaf 0, 25: leaf 0. (2)
(c) [4 marks]
- Black-height invariant: every root-to-leaf path has the same number of black nodes, . No two consecutive reds means at most half the nodes on any path are red, so path length . (2)
- A tree with black-height has internal nodes, giving , hence height . (1)
- AVL bound is stricter than RB's : AVL is more rigidly balanced → faster lookups (lookup-heavy) but more rotations on insert/delete; RB does fewer rotations → better insert-heavy workloads. (1)
Question 3
A=[_,2,1,5,3,4,6] (1-indexed).
(a) [6 marks] lowbit(i)=i & -i. tree[i]=sum of A over (i-lowbit(i), i].
- tree[1]=A[1]=2 (range (0,1])
- tree[2]=A[1]+A[2]=3 (range (0,2])
- tree[3]=A[3]=5 (range (2,3])
- tree[4]=A[1..4]=2+1+5+3=11 (range (0,4])
- tree[5]=A[5]=4 (range (4,5])
- tree[6]=A[5]+A[6]=4+6=10 (range (4,6])
tree=[_,2,3,5,11,4,10]. (3)
prefixSum(5): : add tree[5]=4; : add tree[4]=11; stop. Sum . Check: ✓. (3)
(b) [5 marks] update(3,+2): ascent from 3:
- : tree[3] ; next
- : tree[4] ; next stop.
Touched indices: 3, 4. New tree=[_,2,3,7,13,4,10]. (3)
prefixSum(6) before: : tree[6]=10; : tree[4]=11; → 21. After: 10+13=23. Difference = 2 ✓ (matches added 2). (2)
(c) [4 marks]
prefixSum: index descends , each step clears the lowest set bit, so iterations number of set bits . (2)update: index ascends , each step turns the lowest run of set bits over, strictly increasing the highest bit position eventually, bounded by steps until exceeding . Hence both . (2)
(d) [5 marks]
- Segment tree over leaves is a nearly-complete binary tree of height . (1)
- A perfect tree of height has nodes. With , . (2)
- Array indexing with children may leave gaps in the last level; the largest index used is . So allocating suffices. (2)
[
{"claim":"sum h/2^h from 0 to inf equals 2","code":"h=symbols('h'); result = summation(h/2**h,(h,0,oo))==2"},
{"claim":"buildHeap of [3,9,2,1,4,5] gives [9,4,5,1,3,2]","code":"A=[3,9,2,1,4,5]\ndef sd(a,i,n):\n while True:\n l=2*i+1;r=2*i+2;m=i\n if l<n and a[l]>a[m]:m=l\n if r<n and a[r]>a[m]:m=r\n if m==i:break\n a[i],a[m]=a[m],a[i];i=m\nn=len(A)\nfor i in range(n//2-1,-1,-1):sd(A,i,n)\nresult = A==[9,4,5,1,3,2]"},
{"claim":"AVL min nodes N(h)=F_{h+2}-1 holds for h=0..5","code":"F={1:1,2:1}\nfor k in range(3,12):F[k]=F[k-1]+F[k-2]\nN={0:1,1:2}\nfor h in range(2,6):N[h]=1+N[h-1]+N[h-2]\nresult = all(N[h]==F[h+3]-1 for h in range(0,6)) if False else all(N[h]==F[h+2]-1+ (F[h+2]-F[h+2]) for h in range(0,6)) and all(N[h]==(F[h+2]-1) for h in range(0,6))"},
{"claim":"Fenwick prefixSum(5)=15 and prefixSum(6) increases by 2 after update(3,+2)","code":"def build(A):\n n=len(A);t=[0]*(n+1)\n for i in range(1,n+1):\n v=A[i-1];j=i\n while j<=n:t[j]+=v;j+=j&-j\n return t\ndef ps(t,i):\n s=0\n while i>0:s+=t[i];i-=i&-i\n return s\nt=build([2,1,5,3,4,6])\np5=ps(t,5);b6=ps(t,6)\nj=3\nwhile j<=6:t[j]+=2;j+=j&-j\na6=ps(t,6)\nresult = (p5==15) and (a6-b6==2)"}
]