E ExamMaster

Data Structures & Algorithms · Data Structures & Algorithms

Optimal Binary Search Trees

Choose BST shape to minimise expected search cost: the O(n^3) DP e[i,j] = w[i,j] + \min_r\,(e[i,r-1]+e[r+1,j]), filled by increasing span on a three-key instance.

Four concepts on optimal BSTs — weighted search cost, the e[i,j]=w[i,j]+\min_r recurrence, filling the three-key table to cost 10, and O(n^3) (with Knuth's O(n^2) as a named remark). Running frequencies: A, B, C at 3, 1, 2. Edit distance already lives in dynamic programming; it is not repeated here.

  • Data Structures & Algorithms
  • Hard level
  • 4 concepts

1Expected cost is frequency times depth plus one

Fix sorted keys k_1 < \cdots < k_n and a frequency p_i for each. Any BST on those keys is legal — the in-order is already fixed — but the depths change with the shape. The cost of a tree is \sum_i p_i \cdot (\mathrm{depth}(k_i)+1), counting the comparison at the node itself as one. An optimal BST is a shape that minimises that sum.

A perfectly balanced tree can lose. If one key is requested far more than the others, it belongs near the root even if the two sides then have different sizes. Balance minimises worst-case depth; this DP minimises weighted external path.

Figure. Optimal tree on frequencies 3, 1, 2: A at the root, C to its right, B left of C. Cost 3*1 + 1*3 + 2*2 = 10.

Score one tree

  1. DepthsRoot has depth 0, so it is charged p once. A child is charged 2p.
  2. SumAdd p_i \cdot (\mathrm{depth}_i+1) over every key.
  3. Compare shapesThe legal BSTs on the same keys; pick the cheapest sum.

Root A versus root B

Keys A, B, C with frequencies 3, 1, 2. Cost of (root A, right C with left B) versus (root B, left A, right C).

  • tree A-root: depths 0, 2, 13\cdot 1 + 1\cdot 3 + 2\cdot 2 = 10
  • tree B-root: depths 1, 0, 13\cdot 2 + 1\cdot 1 + 2\cdot 2 = 11
  • cheaperunbalanced A-root, cost 10

Pro tip. B-root is the balanced BST and the worse expected cost. Frequency, not height, is the objective.

An optimal BST minimises
  1. \sum p_i(\mathrm{depth}_i+1) over BST shapes on the given key order
  2. The height of the tree, ignoring frequencies
  3. The Huffman code length of the keys treated as an unordered alphabet

Height-optimal is AVL/red-black. Huffman is allowed to reorder symbols; a BST cannot.

2e[i,j] = w[i,j] plus the best split

Let e[i,j] be the minimum cost of a BST on the contiguous key range k_i,\ldots,k_j, and let w[i,j] = p_i+\cdots+p_j. Choose a root k_r in that range. The left keys k_i,\ldots,k_{r-1} and the right keys k_{r+1},\ldots,k_j must themselves be optimal BSTs — optimal substructure — and every search in the range pays one comparison at k_r, which is exactly w[i,j].

So e[i,j] = w[i,j] + \min_{i \le r \le j}\bigl(e[i,r-1] + e[r+1,j]\bigr). Empty sides are e[i,i-1] = 0. A single key is e[i,i] = p_i. The w[i,j] addend is the charge: every search in the range pays one comparison at the chosen root.

Figure. e[i,j] = w[i,j] plus the min over roots r of e[i,r−1] + e[r+1,j]. On range A,B,C with w=6 and root A, the empty left side is 0 and e[B,C]=4, so e[A,C]=10. The 6 charges one comparison at A for every search in the range.

One cell

  1. Weightw[i,j] = sum of frequencies in the range.
  2. Try each rootFor each r, add the two already-optimal sides.
  3. Charge the rootAdd w[i,j] once. That is the +1 on every depth in the range.

Why +w appears

Range A,B,C has w=6. If the two sides of root A cost 4 together (the optimal e[B,C]), what is e[A,C] and which 6 is being charged?

  • e[B,C]4
  • left of Ae[\emptyset] = 0
  • w[A,C] + 0 + 410
  • the 6one comparison at A for every request in {A,B,C}

Pro tip. The 4 already includes B and C paying for comparisons below A. The 6 makes them (and A) also pay for A.

The w[i,j] term in the optimal-BST recurrence is there because
  1. Every key in the range is compared with the chosen root once
  2. It estimates the height of a balanced tree
  3. It counts the number of possible roots

One comparison at the root per request in the range, total weight w[i,j]. The number of candidate roots is j-i+1, which is the min's domain, not an addend.

3Fill by increasing span

A cell e[i,j] reads only strictly smaller ranges, so the table is filled by increasing j-i. Fill order is increasing span. Span 0 is the diagonal e[i,i]=p_i. Span 1 tries two roots. Span n-1 is the full instance.

On frequencies 3, 1, 2 the span-1 cells are e[A,B]=5 (root A) and e[B,C]=4 (root C). The span-2 cell is e[A,C]=10 (root A), because the three candidate sums are 10, 11 and 11.

Figure. Fill order: diagonal 3,1,2 then span-1 cells 5 and 4 then the full-range 10. Not a BST drawing — a dependence triangle.

Three spans

  1. Span 0e[A]=3, e[B]=1, e[C]=2.
  2. Span 1e[A,B]=4+\min(1,3)=5; e[B,C]=3+\min(2,1)=4.
  3. Span 2e[A,C]=6+\min(4,5,5)=10.

Optimal BST table

def optimal_bst(p):
    n = len(p)
    e = [[0] * (n + 1) for _ in range(n + 2)]
    w = [[0] * (n + 1) for _ in range(n + 2)]
    root = [[0] * (n + 1) for _ in range(n + 1)]
    for i in range(1, n + 1):
        e[i][i] = w[i][i] = p[i - 1]
        root[i][i] = i
    for span in range(1, n):
        for i in range(1, n - span + 1):
            j = i + span
            w[i][j] = w[i][j - 1] + p[j - 1]
            e[i][j] = min(
                e[i][r - 1] + e[r + 1][j] + w[i][j]
                for r in range(i, j + 1)
            )
            root[i][j] = min(
                range(i, j + 1),
                key=lambda r: e[i][r - 1] + e[r + 1][j] + w[i][j],
            )
    return e[1][n], root

All three roots of A,B,C

w[A,C]=6. Compute the three candidate costs and the min.

  • root A: 6 + e[B,C]6+4=10
  • root B: 6 + e[A] + e[C]6+3+2=11
  • root C: 6 + e[A,B]6+5=11
  • e[A,C]10, root A

Pro tip. You cannot fill e[A,C] before e[A,B] and e[B,C]. That dependence is why the loop is on span, not on i then j in the wrong order.

Coding lab. Fill the three-key cost table runs in the app, with checks on your output.

On frequencies 3, 1, 2 the cell e[A,C] equals
  1. 10, from root A
  2. 11, from root B, because that tree is balanced
  3. 6, the weight alone

The three candidates are 10, 11, 11. Weight 6 is only the addend, not the cost.

4O(n³) cells, O(n) roots each

There are O(n^2) ranges (i,j) and each tries O(n) candidate roots, so the straightforward fill is O(n^3) time and O(n^2) space. Storing \mathrm{root}[i,j] lets you rebuild the tree in O(n) after the table exists.

Knuth observed that the optimal roots are monotonic — \mathrm{root}[i,j-1] \le \mathrm{root}[i,j] \le \mathrm{root}[i+1,j] — which cuts the inner search to amortised O(1) per cell and the time to O(n^2). That speedup is a named remark, not a proof this topic invents. For n=3 the cubic and quadratic algorithms do the same six useful cells.

The span triangle already shows the six cells. The new content is the O(n^3) versus O(n^2) row.

What you pay
VersionTimeSpace
Plain DPO(n^3)O(n^2)
Knuth optimisationO(n^2)O(n^2)
Try every BST\Omega(4^n/n^{3/2}) Catalan

Cell count on n = 3

How many (i,j) cells with j ≥ i, and how many root trials in the plain DP?

  • ranges of span 0,1,23+2+1=6 cells
  • root trials: 3 + 2*2 + 33 + 4 + 3 = 10
  • Catalan C_3 trees enumerated5 trees — already more shapes than cells we filled

Pro tip. The DP does not list trees. It lists ranges. That is why 10 root trials beat 5 full-tree scores only for small n, and beat Catalan growth for large n.

Plain optimal-BST DP is O(n^3) because
  1. Each of O(n^2) ranges tries O(n) roots
  2. Each of n keys is compared with all others in a nested loop like Floyd–Warshall on keys
  3. Building one tree is already cubic

Floyd–Warshall is a different O(n^3) (all-pairs on a graph). Rebuilding one tree from the root table is O(n).

Notes

  • An optimal BST is a binary search tree on sorted keys k_1 < \cdots < k_n that minimises expected search cost when key k_i is requested with frequency (or probability) p_i.
  • Cost of a tree: \sum_i p_i \cdot (\mathrm{depth}(k_i)+1). Putting a frequent key deeper than a rare one is exactly what the DP avoids.
  • Let e[i,j] be the min cost of an optimal BST on keys k_i,\ldots,k_j, and w[i,j] = p_i+\cdots+p_j. Then e[i,j] = w[i,j] + \min_{i \le r \le j}(e[i,r-1]+e[r+1,j]), with e[i,i-1]=0 and e[i,i]=p_i.
  • The w[i,j] term appears because every search in the subtree pays one comparison at the root, so the whole weight of the range is charged once on top of the two optimal subtrees.
  • Fill by increasing j-i. Time O(n^3), space O(n^2). Knuth's monotonicity speeds this to O(n^2); the syllabus outcome is the cubic table.

Formulas

  • w[i,j] = p_i + \cdots + p_j.
  • e[i,i] = p_i, e[i,i-1] = 0.
  • e[i,j] = w[i,j] + \min_{i \le r \le j}\bigl(e[i,r-1] + e[r+1,j]\bigr).
  • Time O(n^3) (each of O(n^2) cells tries O(n) roots), space O(n^2).
  • Store \mathrm{root}[i,j] as the r that attained the min, then rebuild the tree.

Exam traps & shortcuts

  • A balanced BST is not automatically optimal. A key with most of the frequency belongs near the root even if that unbalances the shape.
  • The w[i,j] addend is easy to forget. Without it you would be minimising something that is not search cost.
  • Empty subtrees are cost 0, not a missing base case you can skip. e[i,i-1]=0 is what lets a root at an endpoint work.
  • This is not Huffman. Huffman builds a prefix code on unordered symbols; an optimal BST must stay a BST on the given key order.

Reference tables

Frequencies 3, 1, 2. Empty sides cost 0.

Filled e table
RangewBest roote
A3A3
B1B1
C2C2
A,B4A5
B,C3C4
A,B,C6A10

Recap

Night-before optimal-BST pegs.

Cost
\sum p_i(\mathrm{depth}_i+1). Balanced can lose.
Recurrence
e[i,j]=w[i,j]+\min_r(e[i,r-1]+e[r+1,j]).
3,1,2
Optimum 10, root A, B under C. Balanced root-B costs 11.
Time
O(n^3) plain; Knuth O(n^2) is a named speedup, not a proof we invent.

Practise Optimal Binary Search Trees

Reading is free and needs no account. Practice, mocks and progress live in the app.

  • A 5-question practice set that ends the chapter
  • 4 quick checks with worked explanations
  • Timed mocks scored with the real marking scheme
  • Readiness tracked per topic, kept on your device
Continue with Google — freeNo card, no trial. Works offline once installed.