Data Structures · Lecture Notes

Chapter 10: Search Trees

Binary search in a sorted array is fast, but inserting means shifting everything after the new element. A linked list makes insertion easy but forces a search to walk the whole list. Search trees combine the strengths of both. Elements are placed by key so that at every node “left or right?” has exactly one answer, and search, insertion and deletion follow a single path down from the root, at a cost set by the tree's height. We start with the binary search tree (BST) and its operations, see why height decides everything, and then study AVL, red-black and splay trees, which keep the height small by themselves. The last part covers B-trees and B+ trees, built for large data on disk.

Learning outcomes

BST operations
Implement search, insertion and deletion in a binary search tree; answer successor, predecessor and range queries.
Why balance matters
Explain how tree height drives the cost of operations; show why the same keys inserted in different orders give very different trees.
Balancing
Interpret AVL rotations and the red-black tree rules; work out, step by step, the balanced tree that results from a sequence of insertions.
Comparison
Choose among BSTs, AVL, red-black, splay and B-trees according to the intended use.

10.1Search trees

Nobody looks up “Smith” in a phone book by starting at page one. You open it in the middle, see which half the name is in, and discard the other, which works only because the book is sorted. On a computer this is binary search: about log2 n comparisons among n sorted elements.

The weakness of a sorted array is updating: adding an entry shifts everything after it, n moves in the worst case. A linked list inserts and deletes cheaply but cannot jump to its middle, so a search steps through it one element at a time. Search trees sit in between. Being linked, they update by changing a few links; being ordered, they let each search step eliminate part of the candidates.

10.1.1What is a search tree?

First, the vocabulary. A tree consists of nodes joined by edges. The topmost node is the root; nodes without children are leaves. The depth of a node is the number of edges from the root down to it, so the root has depth 0, and the height h of the tree is the depth of its deepest node. A single node has height 0; the empty tree, by convention, −1.

Each node of a search tree holds an element with a key, a comparable value that identifies it: a student ID, a dictionary word, a surname. The rest of the element (name, address, GPA) is stored alongside. To keep examples simple, our keys are mostly integers and nodes hold nothing else.

Definition 10.1

A search tree is a tree whose nodes hold elements kept in an order determined by their keys, arranged so that a search for a key needs to descend into only one subtree at each node. A search therefore starts at the root, chooses one branch per level, and visits at most h + 1 nodes.

That last sentence is the whole chapter in brief. Search, insertion and deletion each follow one path down from the root, at most as long as the height, so they cost O(h): fast while the tree stays short, slow when it grows tall.

6 2 1 4 3 8depth 0depth 1depth 2depth 3
Figure 10.1: A six-node search tree rooted at 6. Depths: 6 is at 0, 2 and 8 at 1, 1 and 4 at 2, 3 at 3. The deepest node is 3, so the height is h = 3.

How tall can an n-node tree be? At worst every node has one child, the tree is a chain, and the height is n − 1. At best every level is full. Depth d holds at most 2d nodes, so a binary tree of height h has at most 1 + 2 + 4 + … + 2h = 2h+1 − 1 nodes, and n ≤ 2h+1 − 1 gives h ≥ log2(n + 1) − 1: about log2 n.

Example 10.1

Store a million keys (n = 106) in a search tree. Perfectly balanced, it has height log2 106 ≈ 19.93, so a search makes about 20 comparisons. Degenerated into a chain, it has height 999,999, and the worst search makes a million. Same keys, same operation, different shape: a ratio of about 50,000.

This gap is why the rest of the chapter exists. A plain BST promises nothing about height. AVL and red-black trees repair themselves locally after each update and guarantee h = O(log n). A splay tree meets that bound on average over long sequences, and a B-tree packs many keys per node, making the tree short and wide.

Common mistake

Counting height in nodes. Height counts edges from the root to the deepest leaf: the path 6 → 2 → 4 → 3 in Figure 10.1 has four nodes but three edges, so the height is 3. Some books count nodes, which shifts every formula by one. Here height always counts edges, and the empty tree has height −1.

10.1.2Basic operations

A search tree offers what any sorted collection does. Table 10.1 lists the operations and our method names.

Table 10.1: Basic search tree operations; examples use the trees in the slides.
OperationWhat it doesExample
SearchReturns true if the given key is in the tree, false otherwise.search(root, 5) → true
InsertionAdds a new element at the right leaf position without breaking the order.insert(root, 7)
DeletionRemoves the element with the given key and preserves the order.delete(root, 40)
TraversalVisits all elements in a fixed order. Inorder traversal yields the keys in sorted order.inorder(root) → 1 2 3 4 6 8

The order supports further queries: the minimum and maximum key; the successor of a key (the smallest larger key) and its predecessor (the largest smaller key); and all keys within a range. Each follows a single path from the root, so everything except traversal takes O(h); traversal visits every node once, in O(n).

A hash table also searches and inserts in constant average time; ordered queries are what set a search tree apart. Because a hash table scatters its keys, questions like “the smallest key above 50” or “the keys from 20 to 45, in order” force it to scan everything. A search tree answers them in O(h) or O(h + s), where s is the number of keys returned.

10.1.3Kinds of search trees

We cover four kinds of binary search tree and two multiway trees. The binary kinds share one ordering rule and differ in how, and how tightly, they control height. Figure 10.2 shows a small example of each.

  • Binary search tree (BST): each key is greater than every key in its left subtree and smaller than every key in its right subtree. No balance guarantee; the shape depends on insertion order.
  • AVL tree: at every node the subtree heights differ by at most 1 (the balance factor); rotations restore the condition when it breaks.
  • Red-black tree: nodes are red or black, and color rules bound the height by 2 log2(n + 1).
  • Splay tree: rotations move each accessed node to the root. One operation can be costly, but the amortized cost is O(log n).
63148 40201030605070
201051530 53148← last accessed
Figure 10.2: Four kinds of binary search tree. (a) BST: root 6; 3, 1, 4 on the left, 8 on the right. (b) AVL: a perfectly balanced seven-node tree, balance factor 0 everywhere. (c) Red-black: black root 20, red 10, black 30, 5 and 15. (d) Splay: the last key accessed, 5, is at the root.

In multiway trees (B-trees and B+ trees) a node holds several keys and more than two children. Designed mainly for data on disk, they are the subject of Section 10.7.

10.2Binary search trees

In a binary tree each node has at most two children, left and right. A binary search tree adds an ordering rule that makes it behave like a “linked” sorted array.

10.2.1The BST property

Definition 10.2

A binary search tree (BST) is a binary tree in which, for every node x, all keys in the left subtree of x are smaller than x.key and all keys in its right subtree are larger. This is the BST property.

“All” matters: the condition relates a node to every descendant, not just its children. In Figure 10.3, the left tree is a valid BST; in the right one, 7 has been added as the right child of 4. That pair is fine, but 7 lies in the left subtree of 6 while being larger than 6, so the tree is not a BST.

6 2 1 4 3 8 6 2 1 4 3 7 8✗
Figure 10.3: Left: a valid BST. Right: 7 added as the right child of 4. It is larger than its parent, as it should be, but also larger than 6 while in the left subtree of 6: not a BST.

So a node's key splits its subtree in two, smaller keys left and larger right, like the middle element of a sorted array. As binary search halves its range, a BST search knows at each node which side to take and discards the other.

Can a key appear twice? Not under this definition, with its strict inequalities. In practice you either reject duplicates, as our code does, or consistently send equal keys to one side, say the right. Search, insertion and deletion must follow the same rule, or an inserted key may never be found again.

Common mistake

Checking the BST property only between parents and children. “Every left child smaller, every right child larger” also holds for the right-hand tree in Figure 10.3, which is not a BST. The correct check carries lower and upper bounds down from the root (Section 10.3.12).

10.2.2Node structure

A BST node has three fields: the data (the key) and references to its left and right children (Figure 10.4). A missing child is null, so a leaf has two null references. Compared with a linked-list node, there are two child references instead of one “next.”

leftdatarightleft childright child
Figure 10.4: The three fields of a binary tree node: left child reference, data and right child reference.

The slides write the node in two forms. The first holds only an int key:

class TreeNode {
    int data;
    TreeNode left, right;

    TreeNode(int data) {
        this.data = data;
        this.left = null;
        this.right = null;
    }
}

The constructor stores the data and sets both children to null, which Java does anyway; the assignments just show intent. The successor, range query and validation code use this class, with the short field names left and right.

The second form is a generic class. The key need not be an integer; any comparable type will do:

class BinaryTreeNode<E extends Comparable<E>> {
    E data;                    // element
    BinaryTreeNode<E> leftChild; // left child
    BinaryTreeNode<E> rightChild; // right child

    BinaryTreeNode(E data) {
        this.data = data;
        // children start out null
    }
}

The bound E extends Comparable<E> means two E objects can be compared with compareTo. Integer, String and LocalDate implement Comparable, so any of them can be a key. a.compareTo(b) is negative if a < b, 0 if equal, and positive if a > b; search, insertion and deletion look only at this sign. Here the fields are leftChild and rightChild.

Each node carries two references besides its data, so n nodes take O(n) memory. Balanced trees add a constant per node, a height for AVL or a one-bit color for red-black, and total memory stays O(n).

10.2.3Minimum and maximum keys

The BST property tells us where the extremes are. A node with a left child cannot be the minimum, since something smaller is on its left. So the minimum is found by going left from the root until a node has no left child, and the maximum by going right (Figure 10.5).

6 2 1 4 3 8min: always leftmax: always right
Figure 10.5: The minimum key 1 is reached by always going left from the root (6 → 2 → 1), the maximum key 8 by always going right (6 → 8).

The slides give both methods together:

BinaryTreeNode<E> minValue(
        BinaryTreeNode<E> node) {
    while (node.leftChild != null)
        node = node.leftChild; // go left
    return node;  // minimum
}
BinaryTreeNode<E> maxValue(
        BinaryTreeNode<E> node) {
    while (node.rightChild != null)
        node = node.rightChild; // go right
    return node;  // maximum
}

minValue starts at the given node and, while it has a left child, moves node there; when the left child is null, it returns the current node. maxValue does the same on the right. Both assume a nonempty tree: if node starts as null, node.leftChild throws a NullPointerException.

No keys are compared; the methods just follow links, one level per iteration, so they take at most h iterations, O(h) time, and O(1) extra memory.

Taking any node rather than the root is deliberate: deletion calls minValue on the right subtree of a two-child node to find its replacement. The same method serves for the whole tree or any subtree.

Common mistake

Writing the condition as while (node != null). The loop then runs past the leftmost node to null, and the method always returns null. Stop one step earlier, at the node with no left child: check that a next node exists before moving.

10.2.4Code trace: minValue and maxValue

We trace the tree of Figure 10.5, built by inserting 6, 2, 8, 1, 4, 3: root 6 with children 2 and 8; 2 with children 1 and 4; 4 with left child 3. Table 10.2 shows node and the condition in each iteration.

Table 10.2: Trace of minValue(root) and maxValue(root).
CallStepnodeConditionResult
minValue16leftChild = 2 ≠ nullgo left
22leftChild = 1 ≠ nullgo left
31leftChild = nullloop ends, return 1
maxValue16rightChild = 8 ≠ nullgo right
28rightChild = nullloop ends, return 8

Note that the deepest node, 3, is not the minimum: it is left of 4, which is right of 2, so 3 > 2. The minimum is in the leftmost node, not the deepest. Figure 10.6 shows three moments of minValue and the result of maxValue.

6node21438 62node1438
621node438 621438node
Figure 10.6: Four moments of the trace. (a) minValue(root): node starts at 6. (b) node = node.leftChild: now at 2. (c) 1 has no left child; the loop ends with minimum 1. (d) maxValue moves from 6 to 8 and stops: maximum 8.

10.2.5Inorder traversal

A traversal visits every node in a fixed order. Binary trees have three classic recursive orders, preorder, inorder and postorder, named for when the root is visited relative to its subtrees. Inorder visits the left subtree, then the root, then the right subtree.

void inorder(BinaryTreeNode<E> d) {
    if (d == null) return;
    inorder(d.leftChild);  // left
    System.out.print(d.data + " ");
    inorder(d.rightChild);  // right
}

On an empty subtree (d == null) the method returns at once: the base case. Otherwise its three lines traverse the left subtree, print the node's data, and traverse the right subtree.

In a BST this order prints the keys in increasing order. Take any node: its whole left subtree, all smaller keys, is printed before it, and its right subtree, all larger keys, after it. Every node thus comes after all smaller keys and before all larger ones, which is exactly sorted output.

Theorem 10.1 (Inorder traversal is sorted)

The inorder traversal of a binary search tree lists its keys in increasing order.

By induction on height. The empty tree gives empty output. For a tree of height h with root r, both subtrees are shorter, so by hypothesis each traversal lists its keys in increasing order. The output is “left list, r, right list,” and by the BST property every left key is below r and every right key above it, so the whole list increases. □

The converse holds too: a binary tree whose inorder traversal is strictly increasing is a BST. So one way to check a tree is to traverse it inorder and test that the output increases.

Each node is printed once and makes two recursive calls, O(n) calls in all (n nodes produce n + 1 empty calls), so the time is O(n). The stack holds at most h + 2 frames, counting the empty call: O(h) memory.

10.2.6Code trace: inorder

inorder(root) on the same tree produces the events of Table 10.3. “Pending” lists, bottom to top, the nodes on the call stack waiting for their left subtrees.

Table 10.3: Trace of inorder(root) (tree: 6(2(1, 4(3, —)), 8)).
StepEventPendingOutput
1inorder(6) → left first: inorder(2)6
2inorder(2) → left first: inorder(1)6, 2
3inorder(1): left is null, returns at once; print 16, 21
4right of 1 is null; frame closes; print 261 2
5right of 2: inorder(4) → left first: inorder(3)6, 41 2
6inorder(3): left and right are null; print 36, 41 2 3
7print 4; right of 4 is null; inorder(4) and inorder(2) close61 2 3 4
8print 6; right subtree: inorder(8)—1 2 3 4 6
9inorder(8): left is null; print 8; right is null—1 2 3 4 6 8

The output 1 2 3 4 6 8 is sorted. 6 is called first but printed fifth, since its four smaller keys are all in its left subtree; 8 is called last and printed last. Figure 10.7 shows four moments.

621d438 62d1438
6214d38 621438
Figure 10.7: Four moments of the inorder trace; printed nodes are orange. (a) Left side done, 1 printed. (b) 2 printed. (c) 4 printed: output 1, 2, 3, 4. (d) All calls finished; output 1 2 3 4 6 8, each node printed once: O(n).
Common mistake

Expecting sorted inorder output from any binary tree. That holds only for a BST; without the ordering rule, the order depends on the tree's shape. And in a BST, preorder and postorder are not sorted either. Only inorder is.

10.3BST operations

All BST operations share one skeleton: start at the root, compare, go left if smaller and right if larger. They differ in where they stop and what follows. Search stops at an equal key or an empty link; insertion hangs a new node on the empty link; deletion removes the found node in one of three ways, depending on its children.

10.3.1Search

Search follows three rules:

  1. Start at the root. If the node's key equals the target, the search succeeds.
  2. If the target is smaller, repeat in the left subtree; if larger, in the right subtree.
  3. If you reach a null link, the key is not in the tree.

The third rule follows from the BST property: a key in the tree must lie in the subtree chosen at each step, since the other subtree's keys are all smaller or all larger. If that subtree is empty, the key is nowhere.

6 4 2 5 8 7 95 < 65 > 4
Figure 10.8: Searching for 5 in the tree with keys 6, 4, 8, 2, 5, 7, 9: left at 6 (5 < 6), right at 4 (5 > 4), and 5 is found.

The slides give a recursive search on the generic node class:

boolean search(BinaryTreeNode<E> root, E data) {
    if (root == null)
        return false;            // absent
    int k = data.compareTo(root.data);
    if (k == 0)
        return true;             // found
    else if (k < 0)
        return search(root.leftChild, data);
    else
        return search(root.rightChild, data);
}

The first if is the base case: an empty subtree has no keys, so the answer is false. Otherwise the target is compared once with the node's data, the result is kept in k, and the method returns true for k == 0 or recurses left for k < 0 and right for k > 0, passing the result up unchanged.

Comparing once and saving the result is clearer and cheaper: compareTo may be costly for a String or a complex object, and two ifs would call it twice.

Common mistake

Comparing object keys with ==, which tests whether two references are the same object, not whether values are equal. With Integer keys, the 1000 in a node and the 1000 searched for are separately boxed, so data == root.data is false and the key is missed. Java caches −128 to 127, so the bug appears only with larger numbers. Use compareTo for order and compareTo(...) == 0 or equals for equality.

Each call descends one level with constant work. A successful search examines one node more than the found node's depth; an unsuccessful one at most h + 1. Both are O(h), using O(h) stack. Since the recursive call comes last, a while loop assigning root = root.leftChild or root = root.rightChild does the same in O(1) extra memory.

10.3.2Code trace: search

We trace a successful search(root, 5) and an unsuccessful search(root, 10) on the tree of Figure 10.8, 6(4(2, 5), 8(7, 9)). Each call opens a new stack frame.

Table 10.4: Trace of search(root, 5) and search(root, 10).
CallrootkDecision
search(root, 5)6−1 (5 < 6)left subtree: search(4, 5)
41 (5 > 4)right subtree: search(5, 5)
50found, return true; the frames at 4 and 6 return true unchanged
search(root, 10)61 (10 > 6)right: search(8, 10)
81 (10 > 8)right: search(9, 10)
91 (10 > 9)right: search(null, 10)
null—9 has no right child: return false; the frames at 9, 8 and 6 return false unchanged

The successful search compared three nodes, one more than the depth of 5. The unsuccessful one also examined three (6, 8, 9) before its fourth call hit an empty link. With height 2, no search can exceed h + 1 = 3 comparisons, and both reached that bound; an unsorted array of the same seven keys would need seven for a miss.

6root425879 6425root879
6root425879 6425879root
Figure 10.9: The search trace. (a) search(root, 5): 5 < 6, k = −1. (b) Third frame: root is 5, k = 0. (c) search(root, 10): 10 > 6, right. (d) root == null below 9: 10 is absent.

10.3.3Insertion

A new key goes where a search for it ends: at a null link. Every node on the way sent the search to the correct side, so that link is the only place the key fits without breaking the BST property. The new node becomes a leaf there.

6 2 1 4 3 5 8insert 5
Figure 10.10: Inserting 5 into the tree with keys 6, 2, 8, 1, 4, 3: left at 6, right at 2, right at 4. The right link of 4 is empty, so 5 becomes a leaf there (dashed).
BinaryTreeNode<E> insert(
        BinaryTreeNode<E> root, E data) {
    if (root == null)
        return new BinaryTreeNode<>(data);
    int k = data.compareTo(root.data);
    if (k < 0)
        root.leftChild = insert(root.leftChild, data);
    else if (k > 0)
        root.rightChild = insert(root.rightChild, data);
    // k == 0: key already present, not inserted
    return root;
}
// usage: root = insert(root, 5);

The key feature is the return value: insert returns the root of its subtree after the insertion. For an empty subtree it creates and returns the new node, now that subtree's root. Otherwise the root stays the same: the method updates a child link with the recursive result and returns its own root.

The line root.leftChild = insert(root.leftChild, data) looks redundant, since the child is already there. Only the assignment at the bottom does real work: in the new node's parent, root.rightChild is still null, and the assignment links in the returned node. Elsewhere each link is rewritten with its own value. These “redundant” assignments save the code from tracking the parent.

The caller follows the same pattern, root = insert(root, 5);. For an empty tree, the first insertion creates the root, and only this assignment stores it.

Common mistake

Calling insert(root, 8); and ignoring the result. Arguments are passed by value, so the caller's root never changes: on an empty tree the new node is lost. On a nonempty tree the bug hides, since an inner root.leftChild = … links the leaf, until the root changes (empty tree, deleted root, rotation at the root) and the tree breaks.

An equal key (k == 0) skips both ifs, so the method returns root unchanged and no duplicate is inserted. Insertion costs the same as search, O(h) time and O(h) stack. The new node is always a leaf and existing nodes never move, which is also why a plain BST's shape depends entirely on insertion order.

10.3.4Code trace: insert

We insert 5 into the tree of Figure 10.10 with root = insert(root, 5). Table 10.5 shows the descent and the return, with the value each frame assigns to its child link.

Table 10.5: Trace of root = insert(root, 5) (tree: 6(2(1, 4(3, —)), 8)).
StepFramekEvent
1root = 6−1go left: insert(root.leftChild, 5)
2root = 21go right: insert(root.rightChild, 5)
3root = 41go right: insert(root.rightChild, 5)
4root = null—new creates node 5 and returns it; no link points to it yet
5root = 4—root.rightChild = node(5): the new node replaces the empty link; return 4
6root = 2—root.rightChild = node(4): link unchanged; return 2
7root = 6—root.leftChild = node(2): link unchanged; return 6
8main—root = 6: the root did not change

The result is 6(2(1, 4(3, 5)), 8), with inorder 1 2 3 4 5 6 8. Three frames opened and closed, and only the right link of 4 really changed. Figure 10.11 shows the new node being created and linked.

6214root38 6214385new
6214root358 6root214358
Figure 10.11: The insertion trace. (a) Right of 4, root = null. (b) Node 5 is created, unlinked. (c) The frame of 4 sets root.rightChild to it. (d) In main, root = 6: the root is unchanged; the new node is a leaf.

10.3.5Successor, predecessor, ceiling and floor

Each element of a sorted list has a “next” and a “previous” neighbor; in a BST they are the successor and the predecessor.

Definition 10.3

The successor of a key x is the smallest key in the tree greater than x; its predecessor is the largest key smaller than x. x itself need not be in the tree. In inorder, they come right after and right before x.

For x in the tree there are two cases (Figure 10.12):

  1. With a right subtree, the successor is its minimum: right once, then left all the way. The keys just above x are in that subtree, and the smallest of them is the successor.
  2. Without a right subtree, it is the nearest ancestor that the root-to-x path passes on its left. The path to 5 is 6 → 4 → 5: it turns left at 6, so 5 < 6, and right at 4, so 4 < 5 cannot qualify. The successor is 6.
6x425879 6425x879
Figure 10.12: The two successor cases in 6(4(2, 5), 8(7, 9)). (a) 6 has a right subtree: right to 8, then left to 7; successor(6) = 7. (b) 5 has none: the nearest ancestor passed on its left is 6; successor(5) = 6.

The code merges both cases into one loop down from the root that keeps the “best candidate so far”:

TreeNode successor(TreeNode root, int x) {
    TreeNode candidate = null; // smallest key greater than x
    while (root != null) {
        if (x < root.data) {
            candidate = root;     // root is a candidate
            root = root.left;  // anything smaller is on the left
        } else
            root = root.right;  // keys greater than x are on the right
    }
    return candidate;
}

At each node, if x < root.data, the node is larger than x and a candidate; a better one, larger than x but smaller than this node, can only be on its left, so we record it and go left. If x ≥ root.data, the node and its left subtree are all at most x and cannot qualify, so we go right. The loop ends at an empty link and returns the last candidate, or null if there was none, meaning no key exceeds x.

Both cases come out automatically. With a right subtree, the loop passes x, goes right once, then left while collecting candidates, ending at that subtree's minimum. Without one, the loop ends right after x, and the last candidate is the nearest ancestor passed on its left. The code does not even need x to be in the tree.

The successor has three relatives, listed in Table 10.6; Java's TreeMap and TreeSet provide all four.

Table 10.6: Ordered neighbor queries; the last column refers to the tree 6(4(2, 5), 8(7, 9)).
OperationMeaningJava TreeMapExample
successorsmallest key greater than xhigherKey(x)successor(5) = 6
predecessorlargest key smaller than xlowerKey(x)predecessor(6) = 5
ceilingsmallest key ≥ xceilingKey(x)ceiling(3) = 4
floorlargest key ≤ xfloorKey(x)floor(3) = 2

The predecessor mirrors the successor: if x > root.data, record the node and go right; otherwise go left. Ceiling differs from successor only on equality: for x in the tree, the ceiling is x itself, the successor the next key. Floor is likewise predecessor with equality allowed. Since 3 is absent from the example tree, ceiling(3) = 4 and floor(3) = 2. All four follow one path, in O(h).

Example 10.2

In the same tree, find floor(root, 7) and predecessor(root, 7).

  1. floor: 7 ≥ 6, candidate 6; right (8). 7 < 8, left (7). 7 ≥ 7, candidate 7; right (null). Result 7: a key in the tree is its own floor.
  2. predecessor: 7 > 6, candidate 6; right (8). Not 7 > 8, left (7). Not 7 > 7, left (null). Result 6, the largest key below 7.

Same path; the only difference is whether 7 itself counts as a candidate.

10.3.6Code trace: successor

successor(root, 5) and successor(root, 6) illustrate the two cases. Each row of Table 10.7 is one loop iteration.

Table 10.7: Trace of successor(root, 5) and successor(root, 6).
CallrootComparisoncandidateDirection
successor(root, 5)65 < 66left
45 ≥ 46right
55 ≥ 56right → null; return 6
successor(root, 6)66 ≥ 6nullright
86 < 88left
76 < 77left → null; return 7

In the first call the candidate, found at the root, never changes: 5 has no right subtree, so its successor is the ancestor 6. In the second it becomes 8, then 7, the minimum of the right subtree of 6. Each call examines three nodes. Figure 10.13 shows four moments.

6candidate4root25879 6candidate425879
64258candidate7root9 642587candidate9
Figure 10.13: The successor trace. (a) 5 < 6: candidate 6, go left. (b) Loop done; successor(5) = 6. (c) successor(root, 6): 6 < 8, candidate 8. (d) 7 is better; loop done, successor(6) = 7.
Common mistake

Taking the successor to be the right child. The right child of 6 is 8, but its successor is 7, the minimum of the right subtree. Conversely, a node without a right subtree may still have a successor: that of 5 is the ancestor 6.

10.3.7Deletion: three cases

Deletion is the most delicate BST operation. A search finds the node in O(h), which is then removed, according to its number of children, without breaking the BST property. The examples use the tree of Figure 10.14.

50 40 30 45 42 60 70
Figure 10.14: The deletion examples' tree, 50(40(30, 45(42, —)), 60(—, 70)). 70 and 30 are leaves, 45 and 60 have one child, 40 and 50 two.
  1. Leaf. Set the parent's link to null. Deleting 70 nulls the right link of 60.
  2. One child. Link the parent directly to the only child. Deleting 45 points 40 at 42; deleting 60 points 50 at 70.
  3. Two children. The parent has one link to offer but the node has two children, so it cannot be removed directly. Instead, replace the node's value with that of its inorder successor (minimum of the right subtree) or predecessor (maximum of the left subtree), then delete that node from its own position. Example: deleting 40.

The one-child case clearly preserves the BST property. 45 and all its descendants lie in the right subtree of 40 and exceed 40, so linking 42 to the right of 40 changes nothing; 42 also stays in the left subtree of 50, below 50. The subtree simply moves up a level, its order intact.

50 40 30 45 42 60 70 50 40 30 45 42 60
50 40 30 45 42 60 70 50 40 30 42 60 70
50 40 30 45 42 60 70 50 40 30 45 42 70
Figure 10.15: Deleting a leaf and one-child nodes, each from the original tree. (a)–(b) Leaf 70 goes; 60's right link becomes null. (c)–(d) 42, the only child of 45, replaces it under 40. (e)–(f) 70, the only child of 60, replaces it under 50.

Why the successor? The replacement must exceed everything in the left subtree and be smaller than everything in the right. The keys adjacent in inorder, predecessor and successor, both qualify. The successor of 40 is 42: as the minimum of the right subtree it is below the other keys there, and being above 40 it exceeds the whole left subtree. Putting 42 in place of 40 keeps the BST property.

Deleting the successor from its old position is always easy. It was found by going left in the right subtree until there was no left child, so it is a leaf or has only a right child: Case 1 or Case 2. The recursion never meets a two-child node again.

50 40 30 45 42 60 70 50 40 30 45 42 60 70
50 42 30 45 42 60 70 50 42 30 45 60 70
50 30 45 42 60 70
Figure 10.16: Deleting 40, which has two children. (a) 40 has children 30 and 45. (b) Inorder successor: to 45, then left to 42. (c) 42 is copied into the node of 40; the old 42 is a duplicate. (d) The old 42, a leaf, is deleted from the right subtree (Case 1). (e) The predecessor, 30, works too; either choice gives a valid BST.

10.3.8The deletion code

With the same “return the subtree's new root” pattern as insertion, one recursive method handles all three cases:

BinaryTreeNode<E> delete(
        BinaryTreeNode<E> root, E data) {
    if (root == null) return null;   // absent
    int k = data.compareTo(root.data);
    if (k < 0)
        root.leftChild = delete(root.leftChild, data);
    else if (k > 0)
        root.rightChild = delete(root.rightChild, data);
    else if (root.leftChild == null)
        return root.rightChild;  // leaf/right child
    else if (root.rightChild == null)
        return root.leftChild;  // left child only
    else {                    // two children
        root.data = minValue(root.rightChild).data;
        root.rightChild = delete(root.rightChild, root.data);
    }
    return root;
}

The first half is a search. If the key is absent (root == null), it returns null, leaving the tree unchanged. For k < 0 or k > 0 it recurses into the corresponding subtree and assigns the result to the child link.

At k == 0 the node is found, and three branches handle the cases:

  • If root.leftChild == null, return root.rightChild. This covers two cases: for a leaf it returns null, emptying the parent's link; for a node with only a right child it returns that child, which the parent then links to directly.
  • If root.rightChild == null (left child only), return the left child.
  • With two children, minValue(root.rightChild) finds the successor, whose value is copied into the node; delete(root.rightChild, root.data) then removes the successor, since root.data now holds its value.

The node is detached when the returned value is assigned to the parent's link: in the leaf and one-child cases the method returns the node's replacement, and the reference to the deleted node disappears, leaving it for the garbage collector. Calls are written root = delete(root, 40);, since a deleted root can only be replaced through this assignment.

The descent costs O(h). In the two-child case minValue and the second delete continue below the node, together within O(h). Deletion takes O(h).

Common mistake

Copying the successor's value into a two-child node but forgetting to delete the successor. Then 42 appears twice and the tree is no longer a BST. Equally wrong is deleting with the old key: delete(root.rightChild, data) looks for 40 in the right subtree and finds nothing. Use the copied value: delete(root.rightChild, root.data).

10.3.9Code trace: three deletions

All three start from the tree of Figure 10.14; Table 10.8 shows the comparisons and returned links.

Table 10.8: Trace of delete(root, 70), delete(root, 45) and delete(root, 40).
CallDescent (k)At the node foundLink changedResulting tree
delete(root, 70)50 (1), 60 (1), 70 (0)no left child → return root.rightChild = nullright link of 60 is null50(40(30, 45(42, —)), 60)
delete(root, 45)50 (−1), 40 (1), 45 (0)left child, no right → return root.leftChild = 42right link of 40 is 4250(40(30, 42), 60(—, 70))
delete(root, 40)50 (−1), 40 (0)two children: successor 42; root.data = 42; delete(subtree at 45, 42): 45 (−1), 42 (0), leaf → nullleft link of 45 is null; the node of 40 now holds 4250(42(30, 45), 60(—, 70))

The inorder sequences of the results are 30 40 42 45 50 60, 30 40 42 50 60 70 and 30 42 45 50 60 70: the deleted key is gone, the rest in order. The root never changed; root = delete(…) in main wrote back 50 each time.

The third trace runs one deletion inside another; Table 10.9 shows it step by step.

Table 10.9: Details of the trace of root = delete(root, 40).
StepFrameEvent
1delete(50, 40)k = −1 (40 < 50): root.leftChild = delete(root.leftChild, 40)
2delete(40, 40)k = 0; two children (30 and 45)
3delete(40, 40)minValue(45): left from 45 to 42; 42 has no left child → successor 42
4delete(40, 40)root.data = 42: the node now holds 42; the tree contains two 42s
5delete(45, 42)k = −1 (42 < 45): root.leftChild = delete(root.leftChild, 42)
6delete(42, 42)k = 0; no left child → return root.rightChild = null
7delete(45, 42)root.leftChild = null: the old 42 is detached; return 45
8delete(42, 40)root.rightChild = node(45) (link unchanged); return the node (value 42)
9delete(50, 40)root.leftChild = the same node; return 50; main: root = 50

The node object holding 40 never left the tree; only its value became 42. What was detached is the leaf holding the old 42. Figure 10.17 shows three moments and the result.

5040root304542successor6070 5042root3045426070
50423045root607042 504230456070
Figure 10.17: delete(root, 40). (a) minValue(root.rightChild): successor 42. (b) root.data = 42: 42 appears twice. (c) The link to the old 42 is now null. (d) Result 50(42(30, 45), 60(—, 70)): one node fewer, BST property intact.
Example 10.3

Starting again from the original tree, delete the root, 50.

  1. delete(50, 50): k = 0; two children (40 and 60).
  2. minValue(60): 60 has no left child, so it is its own successor.
  3. root.data = 60; then delete(subtree at 60, 60): k = 0, no left child → return root.rightChild = 70, a one-child deletion.
  4. The root's right link now points to 70. Result: 60(40(30, 45(42, —)), 70); inorder 30 40 42 45 60 70.

Here the successor was the deleted node's right child and had a right child of its own: a successor need not be a leaf, but it never has a left child.

10.3.10Analysis of BST operations

Every operation so far costs time proportional to the depth of the deepest node it reaches, at worst the height: O(h). So the real question is how tall a BST with n keys gets.

That depends on insertion order. Inserting 4, 2, 6, 1, 3, 5, 7 gives a perfect tree of height 2. Inserting 1, 2, …, 7 puts each key right of the previous one, producing a chain of height 6. Such a degenerate tree is just a linked list that wastes memory.

4 2 1 3 6 5 7balanced: h = 2 ≈ log₂ n 1 2 3 4 5degenerate (chain): h = n − 1
Figure 10.18: Left: a balanced 7-node tree of height 2 (about log2 n). Right: inserting 1, 2, 3, 4, 5 in order gives a chain of height n − 1 = 4.
Table 10.10: BST height and operation cost; the last column is the approximate number of steps for n = 106.
CaseHeight hSearch / insert / deleten = 106
Balanced (best)≈ log2 nO(log n)≈ 20
Random order (average)≈ 2.99 log2 nO(log n)≈ 28 (average)
Degenerate (worst, sorted input)n − 1O(n)106

The middle row is encouraging. With random insertion order, the average depth is about 2 ln n ≈ 1.39 log2 n, some 28 comparisons for a million keys, only about 40% above the best case. The expected height is larger, about 4.31 ln n ≈ 2.99 log2 n, around 60 for a million keys; the table's height column shows this, its last column the average depth. The analysis matches quicksort's: each key, on becoming a root, splits the rest into two random groups.

Real data, however, is seldom random. Records from a file may be sorted already, student IDs grow with enrollment, timestamps only increase. Such input drives a plain BST to its worst case, with every operation O(n), and inserting n sorted keys costs 1 + 2 + … + (n − 1) = n(n − 1)/2 = O(n2) comparisons: half a trillion for a million keys.

Common mistake

Saying “BST search is O(log n).” Only for a balanced tree. A plain BST guarantees nothing about height; the correct bound is O(h), with h = n − 1 in the worst case. O(log n) is guaranteed only by balanced trees such as AVL and red-black trees.

10.3.11Range queries

A range query reports, in order, every key in [lo, hi]: “keys between 5 and 10,” or “students with a GPA from 2.5 to 3.0.” Traversing the whole tree inorder works but takes O(n) however few keys qualify. The BST property lets us do far better by never entering a subtree that cannot hold a key in range, a technique called pruning.

void range(TreeNode k, int lo, int hi) {
    if (k == null) return;
    if (lo < k.data)           // candidates on the left
        range(k.left, lo, hi);
    if (lo <= k.data && k.data <= hi)
        System.out.print(k.data + " ");
    if (k.data < hi)           // candidates on the right
        range(k.right, lo, hi);
}

This is inorder traversal pruned by two tests. All left-subtree keys are below k.data, so if k.data ≤ lo none can be in range and the left subtree is skipped; likewise, if k.data ≥ hi, the right subtree is skipped. The middle if prints the node when it is in range, and the left–node–right order keeps the output sorted.

Table 10.11 traces range(root, 5, 10) on 8(4(2, 6(5, 7)), 12(10, 14)).

Table 10.11: Trace of range(root, 5, 10).
NodeLeft condition 5 < k.dataIn range?Right condition k.data < 10Output
8true → go leftthe node and its right side are handled after the left subtree
4false → 2 is prunednotrue → go right
6true → go leftthe node and its right side are handled after the left subtree
5false → left skippedyestrue → right is null5
6back from the left; print 6; range(7)5 6
7true → left is nullyestrue → right is null5 6 7
8back from the left; print 8; range(12)5 6 7 8
12true → go leftnofalse → 14 is pruned
10true → left is nullyesfalse → right pruned5 6 7 8 10

The output 5 6 7 8 10 is sorted. The visited nodes are 8, 4, 6, 5, 7, 12, 10; 2 and 14 were never visited, because 4 was already below the lower bound and 12 above the upper. Figure 10.19 shows the start and end of the trace.

842657121014lo = 5, hi = 10 842657121014lo = 5, hi = 10
842657121014lo = 5, hi = 10 842657121014lo = 5, hi = 10
Figure 10.19: range(root, 5, 10). (a) 5 < 8: go left. (b) 5 < 4 fails: the left subtree of 4 is pruned. (c) 12 ≥ 10: its right subtree is pruned. (d) Result 5 6 7 8 10; 2 and 14 (faded) are never visited.

For the cost, split the visited nodes into the s nodes in range, all printed, and the others, which lie on the two paths toward the ends of the range, each of at most h + 1 nodes. The total is O(h + s): in a balanced tree of a million keys, a 10-key range takes a few dozen node visits.

10.3.12Is a tree a BST?

To check whether a binary tree is a BST, remember that the property applies to whole subtrees: each node must agree with all its ancestors, not just its parent. Going left adds an upper bound, going right a lower bound, and every node must lie within all the bounds on its path from the root.

boolean isBST(TreeNode k, long min, long max) {
    if (k == null) return true;
    if (k.data <= min || k.data >= max)
        return false;          // out of range
    return isBST(k.left, min, k.data)
        && isBST(k.right, k.data, max);
}
// first call: isBST(root, Long.MIN_VALUE,
//                   Long.MAX_VALUE)

Each node receives an allowed interval (min, max). The root's is unbounded, with Long.MIN_VALUE and Long.MAX_VALUE standing in for ±∞; since keys are ints, these long values never equal a key. A node outside its interval yields false; otherwise its value becomes the upper bound for its left child and the lower bound for its right, and the two results are joined with &&.

Table 10.12 checks the faulty tree on the right, 6(2(1, 4(3, 7)), 8).

Table 10.12: Trace of isBST(root, Long.MIN_VALUE, Long.MAX_VALUE).
CallAllowed intervalCheckResult
isBST(6)(−∞, ∞)6 in rangeleft: isBST(2, −∞, 6)
isBST(2)(−∞, 6)2 in rangeleft: isBST(1, −∞, 2)
isBST(1)(−∞, 2)1 in range; children nulltrue
isBST(4)(2, 6)4 in rangeleft: isBST(3, 2, 4)
isBST(3)(2, 4)3 in range; children nulltrue
isBST(7)(4, 6)7 ≥ 6false
isBST(4) = true && false; isBST(2) = true && falsefalse
isBST(6): left is false; && short-circuits and 8 is never checkedfalse

The interval for 7 is (4, 6): the lower bound because 7 is right of 4, the upper bound because 7 is in the left subtree of 6, a bound carried two levels down from the root. A parent–child check would accept this tree, since 4 < 7. The correct check visits each node once: O(n) time, O(h) stack.

6(−∞, ∞)214378 6214(2, 6)378✓
621437(4, 6)8✓✓✗ 6214378✓✓✗✗✗✗
Figure 10.20: The isBST trace. (a) Root interval (−∞, ∞). (b) For 4, (2, 6). (c) For 7, (4, 6); 7 ≥ 6 gives false. (d) The tree is not a BST.
Common mistake

Starting with the int bounds Integer.MIN_VALUE and Integer.MAX_VALUE. If the tree holds one of those values, the strict inequality rejects a valid tree. The slides' code avoids this with long bounds; alternatively, pass Integer objects and use null for “no bound.”

10.3.13Application: student records

Real applications store whole records in nodes. In the slides' example the key is the student ID and the node holds the entire student object:

public class Student {
    int id;     // key
    String fullName;

    public Student(int id,
                   String fullName) {
        this.id = id;
        this.fullName = fullName;
    }
}
public class StudentNode {
    Student st;           // data
    StudentNode leftChild;
    StudentNode rightChild;

    public StudentNode(Student st) {
        this.st = st;
        // children start out null
    }
}

Student holds the data and StudentNode the tree structure, so the same student object can also live in another structure, a list for example. Insertion and search mirror the integer versions:

StudentNode insert(StudentNode root, Student st) {
    if (root == null) return new StudentNode(st);
    if (st.id < root.st.id)
        root.leftChild = insert(root.leftChild, st);   // continue left
    else if (st.id > root.st.id)
        root.rightChild = insert(root.rightChild, st);   // continue right
    return root;                                   // duplicate ID not inserted
}
Student search(StudentNode root, int id) {
    if (root == null) return null;                // no record
    if (id == root.st.id) return root.st; // found
    if (id < root.st.id)
        return search(root.leftChild, id);         // continue left
    return search(root.rightChild, id);             // continue right
}

With an int key, comparisons use <, > and == directly rather than compareTo. And search returns the student object, with the name and other fields, or null, instead of a boolean.

Example 10.4

Insert (2205, Alice Brown), (1980, Bob Davis), (2410, Carol Evans) and (2205, Duplicate) into an empty tree.

  1. 2205 becomes the root.
  2. 1980 < 2205: inserted to the left of the root.
  3. 2410 > 2205: inserted to the right of the root.
  4. 2205 is already at the root; both tests fail and the record is not inserted. “Alice Brown” stays.

search(root, 2410).fullName gives “Carol Evans,” search(root, 2205).fullName “Alice Brown.” search(root, 1000) goes left at 2205 and at 1980, hits an empty link and returns null.

Java's TreeMap<Integer, Student> does this job out of the box. It maps keys to values using a red-black tree internally: put, get and remove take O(log n), and higherKey, floorKey and subMap answer successor, floor and range queries. Unlike a hand-written BST, it never degenerates, even on sorted input.

10.4Balance and AVL trees

BST operations take O(h), and h can reach n − 1. Is there a cheap way to keep the height O(log n) no matter what? Yes; Adelson-Velsky and Landis showed the first in 1962.

10.4.1Why balance matters

A plain BST's shape follows the insertion order: 1, 2, 3, 4 gives a chain of height 3, yet the same keys fit in trees of height 2, such as 2(1, 3(—, 4)) and 3(1(—, 2), 4) (Figure 10.21). All three trees have inorder 1 2 3 4; they hold the same sorted set in different shapes.

1 2 3 4 2 1 3 4 3 1 2 4h = 3h = 2h = 2
Figure 10.21: The chain 1, 2, 3, 4 (height 3) reshaped into two trees of height 2, rooted at 2 and at 3.
Definition 10.4

A self-balancing search tree, or simply balanced tree, is a BST that guarantees O(log n) height by repairing itself after every insertion and deletion with small local changes: rotations and recoloring.

“Local” is the key word. Rebuilding an unbalanced tree from its inorder sequence would work but costs O(n) per update, destroying the tree's advantage. Balanced trees touch only a few nodes on the updated path, which has length O(log n), with constant work per node, so updates stay O(log n).

10.4.2A balancing example

A larger example shows local repair at work. The tree on the left of Figure 10.22 holds 11 keys; its branches 9 → 14 → 12 and 76 → 54 → 72 → 67 have grown long, giving height 4. Rearranging those two subtrees fits the keys into height 3, with 12 and 72 as new subtree roots.

50 17 9 14 12 23 19 76 54 72 67 50 17 12 9 14 23 19 72 54 67 76height 4height 3 · all |bf| ≤ 1
Figure 10.22: Left: an unbalanced tree of height 4. Right: the same keys at height 3, with |bf| ≤ 1 everywhere. The orange nodes, 12 and 72, are the new subtree roots.

Rearranging leaves the inorder sequence, 9 12 14 17 19 23 50 54 67 72 76, unchanged; only parent–child relations change. In the left subtree of 17, the median of 9, 14, 12 became the root with 9 and 14 as children. On the right, 54, 72, 67 and 76 were rearranged and the middle key, 72, moved up. The rest of the tree was untouched.

AVL and red-black trees make such repairs automatically. An update can disturb only its own path, and the repair runs along that path from the bottom up.

10.4.3AVL trees and the balance factor

The AVL tree, named after the Soviet mathematicians Georgy Adelson-Velsky and Evgenii Landis and published in 1962, was the first self-balancing binary search tree. Its idea is simple: at every node, the two subtree heights differ by at most 1.

Definition 10.5

The balance factor of node x is bf(x) = h(left) − h(right), the difference of its subtree heights, where an empty subtree has height −1. An AVL tree is a binary search tree with bf ∈ {−1, 0, +1} at every node.

A positive balance factor means “left-heavy,” a negative one “right-heavy.” A leaf has bf = −1 − (−1) = 0; a node with only a left child, bf = 0 − (−1) = 1. This is why an empty subtree must count as −1: with 0, a leaf and a one-child node would look alike.

300 200 100 250 40−1 500 302 202 101 50 400AVL ✓not AVL: |bf| = 2 at 20 and 30
Figure 10.23: The number at each node's upper right is its balance factor. Left: a valid AVL tree, 30(20(10, 25), 40(—, 50)), with −1 at 40 and 0 elsewhere. Right: the chain 5, 10, 20 gives 20 and 30 balance factor 2; not AVL.

The right-hand tree has two unbalanced nodes. We fix the lowest, the one nearest the new node, here 20. That shortens its subtree by one, and 30 rebalances by itself.

Each AVL node stores its height, and balance factors are computed from the children's heights. Insertion and deletion proceed as in a plain BST; then, as the recursion unwinds, each node on the path updates its height and checks its balance factor, and a node with |bf| = 2 is fixed by rotation.

Common mistake

Computing the balance factor from subtree node counts. The AVL condition is about height. In 4(2(1, 3), 5) the root has three nodes on the left and one on the right, but heights 1 and 0, so bf = 1: a valid AVL tree.

10.4.4Why is AVL height O(log n)?

The AVL condition is local, so to see that it bounds the height, ask the reverse: how few nodes, N(h), can an AVL tree of height h have? In such a sparsest tree one subtree of the root has height h − 1, the other the least allowed, h − 2, and both are sparsest trees themselves. Hence:

N(0) = 1,   N(1) = 2,   N(h) = 1 + N(h − 1) + N(h − 2)

Table 10.13: Minimum number of nodes in an AVL tree of height h.
hComputationN(h)
0single node1
1root + one child2
21 + N(1) + N(0) = 1 + 2 + 14
31 + N(2) + N(1) = 1 + 4 + 27
41 + N(3) + N(2) = 1 + 7 + 412
51 + N(4) + N(3) = 1 + 12 + 720
61 + N(5) + N(4) = 1 + 20 + 1233
815131211040716011110190120N(0) = 1N(1) = 2N(2) = 4N(3) = 7N(4) = 12
Figure 10.24: The sparsest AVL tree of height 4, with 12 nodes. The root (orange) has the sparsest trees of height 3 (7 nodes) and height 2 (4 nodes) as its subtrees. At every internal node the subtree heights differ by exactly 1.

These numbers grow like Fibonacci numbers; in fact N(h) = F(h + 3) − 1 (with F(1) = F(2) = 1), so N(h) grows exponentially. A simple bound: since N(h − 1) > N(h − 2), N(h) = 1 + N(h − 1) + N(h − 2) > 2 · N(h − 2), so the count at least doubles every two levels, and N(h) > 2h/2.

Theorem 10.2 (AVL height)

An AVL tree with n nodes has height h < 2 log2 n; a tighter Fibonacci argument gives h < 1.44 log2(n + 2). Search, insertion and deletion therefore take O(log n).

A tree of height h has n ≥ N(h) > 2h/2 nodes; taking base-2 logarithms, log2 n > h/2, so h < 2 log2 n. □

Example 10.5

How tall can a 30-node AVL tree be? Height 6 needs at least N(6) = 33 nodes, too many, while height 5 needs only N(5) = 20. The answer is 5. A plain 30-node BST can reach height 29; a perfectly balanced one has height at least 4 (the perfect 31-node tree has height 4).

So an AVL tree is at worst about 44% taller than a perfectly balanced one; with a million keys its height never exceeds 28, compared with a million-long chain for a plain BST.

10.4.5Rotations

The basic repair tool is the rotation, which reverses the parent–child relation between a node and one of its children: the child moves up, the old parent down. To preserve the BST property, one subtree changes owner.

T3T1T2 y xrotateRight(y)T1T2T3 x y same inorder sequence on both sides: T1 · x · T2 · y · T3
T1T2T3 x yrotateLeft(x)T3T1T2 y x same inorder sequence on both sides: T1 · x · T2 · y · T3
Figure 10.25: Top: right rotation at the left-heavy y; x moves up, y becomes its right child, and T2 moves from x to the left of y. Bottom: the mirror-image left rotation. Thick lines are changed links; the inorder sequence is T1 · x · T2 · y · T3 on both sides.

In a right rotation, y is the parent and x its left child, with subtrees T1 and T2; T3 is the right subtree of y. The inorder sequence is T1, x, T2, y, T3. Afterward x is on top with right child y; T1 and T3 keep their places, and T2, whose keys lie between x and y, becomes the left subtree of y. The sequence is unchanged.

Theorem 10.3 (Rotation)

A rotation preserves the inorder sequence and hence the BST property. It changes only three links and takes O(1) time; one side of the rotated subtree gets a level shorter, the other a level longer.

The three links: the left link of y now points to T2, the right link of x to y, and the parent's link to x instead of y. Three assignments suffice for any tree size. T1 rises a level, T3 drops one, T2 stays put, so if T1 is the heavy side of a left-heavy node, a right rotation evens things out.

Common mistake

Losing T2. If x.right = y comes first, the only reference to the old right subtree of x is gone. Move T2 to safety with y.left = x.right first, then write x.right = y.

10.4.6Single rotations: LL and RR

Let z be the lowest unbalanced node after an insertion. There are four cases, named for the first two steps from z toward the new node (L for left, R for right). In LL the new node went into the left subtree of the left child of z; RR is the mirror image. Here z and its child lean the same way, and one rotation suffices.

302201100 200100300
10−220−1300 200100300
Figure 10.26: Single rotations; balance factors at upper right. (a) LL: 10 inserted left of the left child 20; bf(30) = 2, bf(20) = 1. (b) Right rotation at 30: 20 moves up, 30 becomes its right child, all balance factors 0. (c) RR: 30 inserted right of the right child 20; bf(10) = −2, bf(20) = −1. (d) Left rotation at 10.

LL shows up as bf(z) = 2 (left-heavy) with the left child at 1 or 0; the fix is a right rotation at z. RR shows up as bf(z) = −2 with the right child at −1 or 0; the fix is a left rotation at z.

After an insertion, a single rotation returns the subtree to its pre-insertion height. In Figure 10.26, the subtree of 30 had height 1, the insertion of 10 made it 2, and the rotation restored 1. Ancestors never see a change, so one repair per insertion suffices.

10.4.7Double rotations: LR and RL

In the other two cases the new node ends a “zigzag.” In LR it went into the right subtree of the left child of z: z leans left, its child right, and the three nodes form a triangle. A single right rotation at z merely flips the triangle, leaving the tree unbalanced.

The fix takes two rotations: left at the child, straightening the triangle into the LL case, then right at z. The median of the three keys ends on top with the smaller on its left and the larger on its right. RL is the mirror image.

30210−1200 302201100 200100300
10−2301200 10−220−1300 200100300
Figure 10.27: Double rotations. LR: (a) 20 inserted right of 10; bf(30) = 2, bf(10) = −1, a triangle. (b) Left at 10 gives a line (LL). (c) Right at 30: the median 20 is the root. RL: (d) 20 inserted left of 30; bf(10) = −2, bf(30) = 1. (e) Right at 30 gives a line (RR). (f) Left at 10: 20 is the root.
Table 10.14: The four imbalance cases in an AVL tree (z: the lowest unbalanced node).
Casebf(z)Child's bfFix
LL+2left child ≥ 0rotate right at z
RR−2right child ≤ 0rotate left at z
LR+2left child < 0left at the left child, then right at z
RL−2right child > 0right at the right child, then left at z
Common mistake

Choosing the case from bf(z) alone. bf(z) = 2 only says “left-heavy”; LL versus LR depends on the child. Inserting 50, 20, 30 gives bf(50) = 2 and bf(20) = −1: LR. A single right rotation at 50 yields 20(—, 50(30, —)) with bf(20) = −2, still unbalanced; the right answer is 30(20, 50).

10.4.8The AVL code

An AVL node holds a key, a height and two child references. The slides first give three helpers and the right rotation:

int h(AvlNode d) {
    return d == null ? -1 : d.height;
}
int bf(AvlNode d) {
    return h(d.left) - h(d.right);
}
void update(AvlNode d) {
    d.height = 1 +
        Math.max(h(d.left), h(d.right));
}
AvlNode rotateRight(AvlNode y) {
    AvlNode x = y.left;
    y.left = x.right;  // T2 → y.left
    x.right = y;       // y → x.right
    update(y);         // lower node first
    update(x);
    return x;          // new root
}

h returns −1 for an empty subtree, sparing other methods a null check; bf is the difference of the children's heights; update recomputes a node's height from its children's, which must already be correct, so heights are always updated bottom-up.

rotateRight(y) codes Figure 10.25: x is the rising left child, y.left = x.right moves T2, and x.right = y reverses the relation. y is updated before x, since y is now the child and the height of x depends on it. The method returns the new subtree root x, which the caller assigns to the parent's link, the third link.

The slides abbreviate the left rotation as the “left ↔ right mirror”; in full:

AvlNode rotateLeft(AvlNode x) {
    AvlNode y = x.right;
    x.right = y.left;  // T2 → x.right
    y.left = x;        // x → y.left
    update(x);         // lower node first
    update(y);
    return y;          // new root
}

rebalance combines the four cases:

AvlNode rebalance(AvlNode d) {
    update(d);
    if (bf(d) > 1) {       // left-heavy
        if (bf(d.left) < 0)  // LR
            d.left = rotateLeft(d.left);
        return rotateRight(d); // LL
    }
    if (bf(d) < -1) {      // right-heavy
        if (bf(d.right) > 0)  // RL
            d.right = rotateRight(d.right);
        return rotateLeft(d); // RR
    }
    return d;
}
// rotateLeft: mirror of left ↔ right
// insert/delete as in a BST; on return:
//   return rebalance(root);

rebalance(d) updates the height of d. If the node is left-heavy (bf > 1) and its left child leans right, the case is LR: rotate left at the child, storing the result in d.left. In both cases, then rotate right at d. The right-heavy case mirrors this; a balanced node is returned as is.

Insertion changes only the last line of the BST version:

AvlNode insert(AvlNode d, int k) {
    if (d == null) return new AvlNode(k);
    if (k < d.key)
        d.left = insert(d.left, k);
    else if (k > d.key)
        d.right = insert(d.right, k);
    else return d;          // no duplicates
    return rebalance(d);
}

return rebalance(d) replaces return d, so as the recursion unwinds, every node from the new leaf to the root updates its height and is fixed on the spot if unbalanced. Deletion likewise adds rebalance to the returns of BST deletion.

The descent covers O(log n) levels with constant work per rebalance and rotation, so insertion and deletion take O(log n). Insertion needs at most one repair (a single or double rotation). In deletion a repair may shorten the subtree and push the imbalance up, so there may be a rotation at every level, O(log n) in all.

10.4.9Code trace: rotateRight and rotateLeft

First an LL case: inserting 5 into 40(20(10, 30), 50). 5 goes down 40 → 20 → 10 and lands left of 10. On the way back rebalance(10) and rebalance(20) find bf 1 and do nothing; rebalance(40) finds bf = 2 with left child 20 at 1, so it calls rotateRight(40).

Table 10.15: Trace of rotateRight(y) with y = 40 (LL case).
LineEffectChanged
x = y.leftthe child that moves up is 20; no link has changed yet—
y.left = x.rightthe left link of 40 now points to T2, that is, 30; only x still reaches 20link 1
x.right = ythe right link of 20 points to 40 instead of 30link 2
update(y)40.height = 1 + max(0, 0) = 1 (was 3)height
update(x)20.height = 1 + max(1, 1) = 2 (unchanged, but recomputed)height
return xthe new subtree root is 20; the caller assigns it to its own linklink 3

The result is 20(10(5, —), 40(30, 50)), with balance factors 0 at 20 and 40, 1 at 10, 0 at the leaves. The inorder sequence 5 10 20 30 40 50 is unchanged. 40 dropped from height 3 to 1, and the new subtree root 20 has height 2, as the subtree did before the insertion.

402h=3201h=2101h=150h=0300h=0500h=0 40h=3y20h=2x10h=15h=030h=050h=0
40h=3y20h=2x10h=15h=030h=050h=0 200h=2x101h=150h=0400h=1y300h=0500h=0
Figure 10.28: rotateRight(40), with balance factor and height beside each node. (a) After inserting 5: bf(40) = 2, bf(20) = 1, LL. (b) y.left = x.right: 40's left link points to 30. (c) x.right = y: 40 is the right child of 20. (d) New subtree root 20; all balance factors in {−1, 0, 1}.

The mirror-image RR case: inserting 60 into 20(10, 40(30, 50)). rebalance(20) finds bf = −2 with right child 40 at −1 and calls rotateLeft(20): y = x.right is 40, x.right = y.left points 20 at 30, y.left = x makes 20 the left child of 40, and the updates give 20 height 1 + max(0, 0) = 1 (was 3) and 40 height 1 + max(1, 1) = 2. The result is 40(20(10, 30), 50(—, 60)).

20−2h=3100h=040−1h=2300h=050−1h=1600h=0 400h=2y200h=1x100h=0300h=050−1h=1600h=0
Figure 10.29: rotateLeft(20). (a) After inserting 60: bf(20) = −2, bf(40) = −1, RR. (b) New subtree root 40, with 20 as its left child and 30 as the right child of 20.
Common mistake

Updating heights in the wrong order after a rotation. Calling update(x) first computes the height of x from the stale height of y (3 in the LL example), giving 4; the balance factors above are then wrong and trigger needless rotations. Update the lowered node first, then the new root.

10.4.10Code trace: rebalance

Now a double rotation in code, the LR case: inserting 25 into 50(20(10, 30), 60). 25 goes down 50 → 20 → 30 and lands left of 30, and rebalance runs at each node on the way back.

Table 10.16: The rebalance calls on the way back after inserting 25 (LR case).
CallHeightbfDecision
rebalance(30)0 → 11no rotation, return d
rebalance(20)1 → 20 − 1 = −1no rotation, return d
rebalance(50)2 → 3h(20) − h(60) = 2 − 0 = 2left-heavy; bf(20) = −1 < 0 → LR
d.left = rotateLeft(20)30 rises; 25 moves right of 20triangle → line; bf(50) still 2
return rotateRight(50)30 becomes the root; 50 goes to its right and takes its old right (null) as leftnew subtree root 30

The result is 30(20(10, 25), 50(—, 60)), back to the pre-insertion height 2, with balance factors 0, 0, −1 at 30, 20, 50. Assigning the first rotation's result to d.left is essential: only then does 50 point to the new subtree root 30.

502h=3d20−1h=2100h=0301h=1250h=0600h=0 502h=3d20−1h=2100h=0301h=1250h=0600h=0
502h=3d302h=2200h=1100h=0250h=0600h=0 300h=2200h=1100h=0250h=050−1h=1600h=0
Figure 10.30: rebalance(50). (a) Height 3, bf = 2. (b) bf(20) = −1: the left child leans right, LR. (c) rotateLeft(20): 30 moves up, the triangle becomes a line. (d) rotateRight(50): 30 is the new subtree root; height 3 → 2.

10.4.11An AVL insertion example

Insert 10, 20, 30, 40, 50, 25 into an empty AVL tree, input on which a plain BST would build (except for the last key) a right-leaning chain. Table 10.17 shows each step.

Table 10.17: Inserting 10, 20, 30, 40, 50, 25 into an empty AVL tree.
InsertDescentImbalance on the way backRotationResulting tree
10empty tree——10
2010 → rightbf(10) = −1, fine—10(—, 20)
3010 → 20 → rightbf(10) = −2, bf(20) = −1: RRleft at 1020(10, 30)
4020 → 30 → rightbf(30) = −1, bf(20) = −1, fine—20(10, 30(—, 40))
5020 → 30 → 40 → rightbf(30) = −2, bf(40) = −1: RRleft at 3020(10, 40(30, 50))
2520 → 40 → 30 → leftbf(20) = −2, bf(40) = 1: RLright at 40, left at 2030(20(10, 25), 40(—, 50))

Can 30 and 20 both be unbalanced when 50 arrives? No. rebalance(40) runs first, then rebalance(30), which fixes 30 and restores its subtree's old height of 1. By the time rebalance(20) runs, its height stays 2 and bf(20) = −1: fixing the lowest unbalanced ancestor leaves everything above untouched.

The last key, 25, lands left of 30. bf(30) = 1 and bf(40) = 1 are fine, but bf(20) = h(10) − h(40) = 0 − 2 = −2 with the right child 40 leaning left: RL. Rotating right at 40 (30 moves up) and then left at 20 brings 30 to the root and moves 25 right of 20. Six insertions give height 2; a plain BST would have height 4.

10−2d20−1300 200100300
20−2d100401301250500 30020010025040−1500
Figure 10.31: Four moments of the AVL trace. (a) 30 inserted: bf(10) = −2, RR. (b) Left rotation at 10; 20 at the root. (c) 25 inserted: bf(20) = −2, bf(40) = 1, RL. (d) After two rotations 30 is the root; all balance factors in {−1, 0, 1}.
Example 10.6

What happens when 1, 2, …, 7 are inserted in order into an empty AVL tree? Every rotation is RR.

  1. Inserting 3 triggers a left rotation at 1: 2(1, 3).
  2. Insert 4: 2(1, 3(—, 4)); no imbalance.
  3. Inserting 5 triggers a left rotation at 3: 2(1, 4(3, 5)).
  4. Inserting 6 makes bf = −2 at the root 2; a left rotation at 2 gives 4(2(1, 3), 5(—, 6)).
  5. Inserting 7 triggers a left rotation at 5: 4(2(1, 3), 6(5, 7)).

Four rotations produce the perfect tree of height 2. Sorted input, a plain BST's worst case, gives the AVL tree its best shape.

10.5Red-black trees

An AVL tree keeps a tight balance, and pays for it: after a deletion the imbalance can climb to the root, with a rotation at every level. A red-black tree accepts looser balance in exchange for very few rotations, doing most repairs by recoloring. Guibas and Sedgewick defined it in today's color terms in 1978; Java's TreeMap and TreeSet and C++'s std::map use it.

10.5.1The rules

Definition 10.6

A red-black tree is a binary search tree whose nodes are colored red or black, subject to five rules:

  1. Every node is red or black.
  2. The root is black.
  3. All leaves, the empty children (NIL), count as black.
  4. A red node has black children: no two reds in a row.
  5. Every path from a node down to the NIL leaves below it has the same number of black nodes, the node's black height.

Rule 3 just means that in code null children count as black, so rules 4 and 5 still make sense where a child is missing. When counting black nodes we count the root but not the NIL leaves.

13 8 1 6 11 17 15 25 22 27
Figure 10.32: A valid red-black tree; small black squares are NIL leaves. Black: 13 (root), 1, 11, 15, 25. Red: 8, 17, 6, 22, 27. Every root-to-NIL path has 2 black nodes (root included, NIL excluded).

Check the rules on Figure 10.32. The red 8 and 17 have black children (1, 11 and 15, 25), and the red leaves 6, 22, 27 have NIL children, so rule 4 holds. For rule 5: 13 → 8 → 1 → NIL has blacks 13 and 1; 13 → 8 → 1 → 6 → NIL the same, since 6 is red; 13 → 17 → 25 → 27 → NIL has 13 and 25. Two on every path.

Common mistake

Checking rule 5 only on the longest and shortest paths. Black counts must match on every path down to NIL, including the empty side of a one-child node. Take a black root 20 with black children 10 and 30, and a black 25 as left child of 30: 20 → 10 → NIL has 2 blacks, 20 → 30 → 25 → NIL has 3. Rule 5 fails.

10.5.2How the rules guarantee balance

The height bound comes from two rules acting together. Let b be the root's black height. Every root-to-NIL path has exactly b black nodes, so the shortest, all black, has b nodes. The longest also has b blacks, with reds in between, but rule 4 allows at most one red after each black, so it has at most 2b nodes.

1052015302540shortest: 2longest: 4 = 2·2
Figure 10.33: With black height 2, the shortest path is 10 → 5 (2 nodes, both black) and the longest 10 → 20 → 30 → 40 (4 nodes, alternating colors): twice as long.
Theorem 10.4 (Height of a red-black tree)

In a red-black tree the longest root-to-NIL path is at most twice the shortest, and with n internal nodes the height is h ≤ 2 log2(n + 1).

The first claim follows from the count above. For the second, a node of black height b has at least 2b − 1 internal nodes in its subtree, by induction: black height 0 means a NIL, with 20 − 1 = 0 nodes; otherwise each child has black height at least b − 1 and thus at least 2b−1 − 1 nodes, for a total of at least 2(2b−1 − 1) + 1 = 2b − 1. At the root rule 4 gives b ≥ h/2, so n ≥ 2h/2 − 1 and h ≤ 2 log2(n + 1). □

Search, insertion and deletion thus take O(log n). Fix-up uses at most 2 rotations after an insertion and 3 after a deletion; the rest is recoloring. The height bound is looser than AVL's (about 1.44 log2 n), so the tree can be somewhat taller, but updates are cheaper and each node needs only one extra bit.

10.5.3Insertion and the fix-up cases

Insertion has two phases. First the new node is attached as a leaf by BST insertion and colored red: a black node would add a black to its paths and break rule 5, whereas a red one changes no black height. Only rule 4 can break, if the parent is red too. The second phase repairs that.

The fix-up involves four nodes (Figure 10.34): the new node x, its parent p, grandparent g and uncle u, the parent's sibling. A red parent cannot be the root, so g exists, and it is black, or two reds would already have been adjacent. The uncle's color decides what happens.

g p x ugrandparentparentunclenew node
Figure 10.34: The roles in the fix-up: new node x, parent p, grandparent g, uncle u.
Table 10.18: Fix-up cases after an insertion (when the parent is red).
CaseActionThen
Red uncleRecolor: p and u black, g red.The problem may move up to the grandparent; set x = g and continue the loop.
Black uncle, triangleIf x–p–g zigzags, rotate at p; x and p swap roles.Becomes the line case.
Black uncle, lineRotate at g; the old parent becomes black, the old grandparent red.Done.

With a black parent nothing needs fixing. At the end the root is always colored black, and a NIL uncle counts as black.

Recoloring preserves rule 5: g goes from black to red and p, u from red to black, and every path down from g passes through exactly one of p, u, so it still has one black here. But a red g may now have a red parent, breaking rule 4 higher up, so the loop continues from g. The problem climbs two levels per step and at worst ends at the root, which is then colored black.

Recoloring is no use when the uncle is black: making g red would remove a black from the uncle's paths. Rotate instead. In the line case (x and p are children on the same side), rotate at g; p rises and turns black, g drops and turns red. In the triangle case, first rotate at p to get a line, just like AVL single and double rotations. The subtree root is then black and the loop ends.

10.5.4The fix-up code

A red-black node has, besides the key and children, a color and a parent reference that lets the fix-up climb. The slides' fix-up code:

void fixUp(Node x) {       // x: new, red
    while (x != root && isRed(x.parent)) {
        Node p = x.parent, g = p.parent;
        Node u = (p == g.left) ? g.right : g.left;
        if (isRed(u)) {           // red uncle
            p.color = u.color = BLACK;
            g.color = RED;
            x = g;                // problem moves to g
        } else {                  // black uncle
            if (isTriangle(x, p, g)) { // zigzag
                rotate(p, x);     // make it a line
                Node t = x; x = p; p = t;
            }
            rotate(g, p);         // line
            p.color = BLACK; g.color = RED;
        }
    }
    root.color = BLACK;
}

The helpers and the rest of the insertion are given as comments:

// Insert: add as a leaf as in a BST,
// link the parent, color it red:
//   x = bstInsert(key);
//   x.color = RED; fixUp(x);

// NIL (null) counts as black
boolean isRed(Node n) {
    return n != null
        && n.color == RED;
}
// is x–p–g a zigzag (triangle)?
boolean isTriangle(Node x, Node p,
                   Node g) {
    return (p == g.left)
        != (x == p.left);
}
// rotate(a, b): b is a child of a;
// moves b up into a's place (right
// rotation if b is on the left, left
// if on the right; parent links too)

isRed(n) treats null as black, so NIL uncles need no special case. isTriangle(x, p, g) compares “is p a left child?” with “is x a left child?”; different answers mean a zigzag. rotate(a, b) lifts b, a child of a, into the place of a (right rotation if b is on the left, left if on the right), updating parent links. The traces cite lines of fixUp: 2 is the loop test, 5 the uncle check, 10 the triangle check, 14 the rotation at g, and 18 the coloring of the root.

Node t = x; x = p; p = t; swaps roles after the triangle rotation, which moved x up and p down, so that rotate(g, p) rotates the upper node.

BST insertion is O(log n), and the loop climbs at least two levels per iteration, so it runs at most O(log n) times. Rotations happen only in the last iteration, since the black-uncle branch ends the loop, and number at most two.

Common mistake

Forgetting root.color = BLACK after the loop. Recoloring can turn the root red, after which the loop stops because x is the root, leaving rule 2 broken. This line is also the only way the black height of the whole tree grows.

10.5.5Code trace: 10, 20, 30, 15

Insert 10, 20, 30, 15 into an empty red-black tree. A letter after a key gives its color, R (red) or B (black): R, B.

Table 10.19: Trace of fixUp while inserting 10, 20, 30, 15.
InsertLoopp, g, uActionResult
10x is the root → not entered—line 18: root black10B
20parent 10 is black → not entered——10B(—, 20R)
30parent 20 is red → entered20, 10, NIL (black)line: rotate(10, 20) to the left; 20 black, 10 red20B(10R, 30R)
15parent 10 is red → entered10, 20, 30Rred uncle: 10 and 30 black, 20 red; x = 20; x is the root → loop ends; line 18: 20 black20B(10B(—, 15R), 30B)

For 30 the uncle is NIL, hence black, and 10 → 20 → 30 is a line, so isTriangle is false and line 14 rotates left at the grandparent, as in the AVL RR fix. After recoloring, the subtree root 20 is black and the loop ends.

For 15, the uncle 30 is red, so recoloring suffices. The grandparent 20 turns red and the loop moves to it, but 20 is the root, so the loop stops and line 18 makes it black again. The tree's black height rose from 1 to 2.

102030x 20p10g30x
20g10p15x30u 20x101530
Figure 10.35: Four moments of the first trace. (a) 30 inserted red under the red 20. (b) After the left rotation at 10 and recoloring: 20 black, 10 and 30 red. (c) 15 inserted; the uncle 30 is red, so only colors change: 10, 30 black, 20 red. (d) The root is black again; black height +1.

10.5.6Code trace: 12 and 5

Next insert 12, then 5, into 20B(10B(—, 15R), 30B): 12 shows the triangle case, 5 recoloring that moves the problem up.

Table 10.20: Trace of fixUp while inserting 12 and 5.
InsertPositionp, g, uActionResult
12left child of 1515R, 10B, NILblack uncle, triangle: rotate(15, 12) to the right; roles swap (x = 15, p = 12); rotate(10, 12) to the left; 12 black, 10 red20B(12B(10R, 15R), 30B)
5left child of 1010R, 12B, 15Rred uncle: 10 and 15 black, 12 red; x = 12; parent 20 is black → loop ends20B(12R(10B(5R, —), 15B), 30B)

For 12, 15 is the right child of 10 and 12 the left child of 15: a zigzag. Rotating right at 15 makes the line 10 → 12 → 15, though two reds remain adjacent; after the role swap, rotating left at 10 lifts 12 to the subtree root, black, with 10 red. This is the AVL RL fix.

For 5, the uncle 15 is red. Recoloring turns 12 red, and the loop stops at 12 because its parent 20 is black. Every path now has 2 black nodes: 20 and 10 on 20 → 12 → 10 → 5 → NIL, 20 and 30 on 20 → 30 → NIL.

20101512x30 NILu2010g12x15p30
2012p10g15x30 2012x1051530
Figure 10.36: Four moments of the second trace. (a) 12 inserted as left child of the red 15. (b) Right rotation at 15: the triangle becomes a line. (c) Left rotation at 10 and recoloring: 12 black, 10 and 15 red. (d) 5 inserted and recolored; all rules hold: 20B(12R(10B(5R, —), 15B), 30B).
Example 10.7

Insert 1 to 10 in order into an empty red-black tree. The code performs five recolorings and five rotations and produces 4B(2B(1B, 3B), 6B(5B, 8R(7B, 9B(—, 10R)))), of height 4 and black height 3. A plain BST would give a chain of height 9, an AVL tree height 3: the red-black tree is one level taller than AVL, but both are logarithmic.

10.5.7Deletion in balanced trees

Deletion in both AVL and red-black trees begins with plain BST deletion; a two-child node is replaced by its successor or predecessor, so the node actually removed is always a leaf or has one child. Its removal may break the AVL height condition or the red-black black-height rule.

  • AVL repair: Update heights from the removed node up to the root, rotating (LL, RR, LR or RL) at every node with |bf| = 2. Unlike insertion, a rotation can leave the subtree shorter than before and unbalance an ancestor, so several ancestors may need repair.
  • Red-black repair: Deleting a red node changes no black counts. Removing a black node leaves its paths one black short, which recoloring and rotations fix according to the colors of the sibling and its children. Textbooks call this the “double black” fix-up; the deficit can climb to the root, but there are at most three rotations.
Table 10.21: Cost of deletion in balanced trees.
StructureChecking upwardRotationsTotal
AVLO(log n)up to O(log n)O(log n)
Red-blackO(log n)at most 3O(log n)

Neither insertion nor deletion disturbs the BST order. Balancing changes only the tree's shape and the auxiliary data (height or color), so an inorder traversal always gives the same sorted list.

10.5.8AVL or red-black?

Both perform every operation in O(log n); the right choice depends on the workload. Table 10.22 summarizes the differences.

Table 10.22: AVL and red-black trees compared.
PropertyAVLRed-black
Balance criterion|bf| ≤ 1 at every nodecolor rules, equal black height
Maximum height≈ 1.44 log2 n2 log2(n + 1)
SearchO(log n), slightly faster (shorter tree)O(log n)
Rotations per insertionat most 2 (one single or one double)at most 2
Rotations per deletionup to O(log n)at most 3
Extra data per nodeheight (or balance factor)1-bit color
Preferred forsearch-dominated workloadsfrequent insertions and deletions, libraries

In practice the difference is often small. A dictionary built once and queried millions of times gains a few comparisons per search from AVL's shorter tree; a general-purpose library class with frequent updates benefits from red-black's cheaper ones, which is why the Java and C++ standard libraries use red-black trees.

10.6Splay trees

AVL and red-black trees store balance information in each node and use it to repair the tree after each operation. A splay tree stores none and does not even try to stay balanced; instead, each access moves the accessed node to the root. Proposed by Sleator and Tarjan in 1985, it is also called a self-adjusting BST.

10.6.1A self-adjusting tree

Definition 10.7

A splay tree is a binary search tree that, after every access (search, insertion, deletion), moves the accessed node to the root through a sequence of rotations, an operation called splaying. Nodes store no balance information.

A splay tree has no height guarantee and can even become a chain, whose bottom node costs O(n) to reach. But that access also roughly halves the depth of the nodes on its path. Sleator and Tarjan showed that any m operations from an empty tree take O(m log n) in total, an amortized cost of O(log n) per operation: the bound covers the whole sequence, with cheap operations paying for expensive ones.

The structure has three notable properties:

  • Locality: Frequently accessed keys stay near the root, so the tree acts like a cache and can beat balanced trees when accesses concentrate on a few keys.
  • Simplicity: No extra fields; one splay routine serves all operations, applied to the node found, the node inserted, or the node about to be deleted.
  • Drawback: Even a search modifies the tree, a problem when several threads read it, and no single operation has a time guarantee, which real-time systems may not accept.

10.6.2Zig, zig-zig and zig-zag

Splaying repeats three kinds of step until the accessed node x is the root; which step applies depends on its parent p and grandparent g.

20 10 5 15 30 x = 10, its parent p = 20 is the root rotateRight(p) 10 5 20 15 30 x is the root; 15 moved to the left of p
30 20 10 x, p, g on the same side (left-left) rotate(g) 20 10 30 grandparent g = 30 first rotate(p) 10 20 30 then parent p = 20
30 10 20 x right, p left child (zigzag) rotateLeft(p) 30 20 10 parent p = 10 first rotateRight(g) 20 10 30 then grandparent g = 30
Figure 10.37: Splay steps. Zig: x = 10 under the root p = 20; one rotation, and 15 moves left of p. Zig-zig: x, p, g all left; rotate at 30, then 20. Zig-zag: x a right child, p a left child; rotate at 10, then 30.
  • Zig: p is the root. One rotation at p makes x the root; this can only be the last step.
  • Zig-zig: x and p are children on the same side. Rotate at the grandparent first, then the parent.
  • Zig-zag: they are on opposite sides. Rotate at the parent, then the grandparent, as in an AVL double rotation.

The zig-zig order differs from AVL and matters. Rotating at the grandparent first “folds” the path, shortening the upper chain too; single rotations from the parent also bring x to the root but can leave the chain long. The amortized bound relies on the grandparent-first order.

Common mistake

Rotating the parent first in zig-zig. That is plain move-to-root, which lacks the amortized O(log n) guarantee: repeatedly accessing the bottom of a sorted chain keeps the chain long, and each access can cost O(n). Rotate at g first, then p.

The slides' code puts the three steps in one loop:

void splay(Node x) {
    while (x != root) {
        Node p = x.parent, g = p.parent;
        if (g == null)                // zig
            rotate(p, x);
        else if (sameSide(x, p, g)) {  // zig-zig
            rotate(g, p);   // grandparent first
            rotate(p, x);
        } else {                      // zig-zag
            rotate(p, x);   // parent first
            rotate(g, x);
        }
    }
}

rotate(a, b) is the single rotation from the red-black code, lifting b, a child of a, into the place of a. sameSide(x, p, g) checks whether x hangs on the same side of p as p of g. The zig-zag branch ends with rotate(g, x) because after the first rotation x has replaced p as the child of g.

10.6.3Code trace: splay(50) and splay(10)

Start from 80(40(20(10, 30), 60(50, 70)), 90) and access 50, then 10.

Table 10.23: Trace of splay(50) followed by splay(10).
AccessIterationp, gStepRotations
50 (path 80 → 40 → 60 → 50)160, 40zig-zag (50 is a left child, 60 a right child)right at 60, then left at 40
280, nonezigright at 80; 50 is the root
10 (path 50 → 40 → 20 → 10)120, 40zig-zig (both left children)right at 40 first, then right at 20
250, nonezigright at 50; 10 is the root

After splay(50) the tree is 50(40(20(10, 30), —), 80(60(—, 70), 90)): 50 rose from depth 3 to the root, the old root 80 dropped a level taking 90 from depth 1 to 2, and 40, 20, 60, 70 kept their depths.

After splay(10) it is 10(—, 50(20(—, 40(30, —)), 80(60(—, 70), 90))). 10 is the root, and 50, accessed just before, is still at depth 1. Because zig-zig rotated at 40 first, 40 moved below 20 and took 30 as its left child.

80402010306050x7090 8050x40g20103060p7090
50x4020103080607090 5010x20p40g3080607090
10x5020403080607090
Figure 10.38: The splay trace. (a) splay(50) searches 80 → 40 → 60 → 50. (b) After the zig-zag, 50 is above 40 and 60. (c) After the zig, 50 is the root. (d) splay(10), zig-zig: rotations at 40, then 20. (e) After the zig, 10 is the root; 50 is at depth 1.

One splay costs time proportional to the accessed node's depth, up to O(n), but the constant reshaping keeps the average over long sequences at O(log n).

10.7Multiway search trees

So far every tree has been binary, which suits main memory, where following a link takes nanoseconds. On disk, reading a block is thousands of times slower than reading memory, hundreds of thousands on old spinning disks. A binary tree indexing a billion records has height at least 30; with one disk read per node, a search costs 30 accesses. Multiway trees cut this to a few.

10.7.1The B-tree

Definition 10.8

A B-tree is a balanced multiway search tree whose nodes hold several keys and many children. In a B-tree of order (degree) m:

  • A node holds at most m children and m − 1 keys.
  • Every internal node other than the root has at least ⌈m/2⌉ children.
  • Keys in a node are sorted. An internal node with k keys has k + 1 children, and the child between two adjacent keys holds the keys between them.
  • All leaves are at the same depth.

A node's keys act as boundaries between its children. The root [20, 40] in Figure 10.39 sends keys below 20 left, keys between 20 and 40 to the middle, and larger keys right: the BST property generalized. A BST resembles a B-tree of order 2, but the B-tree's strength is the rule that all leaves share one depth, which keeps it perfectly balanced.

20405102530355060searching for 30: 20 < 30 < 40 → middle child
Figure 10.39: A B-tree of order 4: root [20, 40], leaves [5, 10], [25, 30, 35], [50, 60]. A search for 30 finds 20 < 30 < 40 at the root and goes to the middle child.

Why? Each node fills one disk block, which holds hundreds of keys. With order 200, a tree of height 3 holds 2004, over a billion keys, and a search reads four blocks, fewer from disk since the root usually stays in memory. Being wide and shallow, B-trees, introduced by Bayer and McCreight in 1972, underlie database and file-system indexes.

A B-tree of order 4 is a 2-3-4 tree, whose nodes have 2, 3 or 4 children, and it is equivalent to a red-black tree: merging each black node with its red children yields a 2-3-4 tree, and “equal black height” becomes “all leaves at the same depth.”

10.7.2Search and insertion in a B-tree

Search generalizes BST search. At each node the keys are compared with the target; if it is not found, the search descends to the child for its interval. One node, one disk block, is read per level, so the cost is O(logm n) reads; binary search within a node takes O(log m) comparisons, O(log n) in total.

Insertion always starts at a leaf: follow the search path and put the key in sorted position. If the leaf then reaches m keys, it splits: the median moves up to the parent and the remaining keys form two nodes. The parent, gaining a key and a child, may split in turn, and so on up to the root, whose split creates a new root and adds a level.

51025303550602040root: 20 < 30 < 40 → go to middle child 51025303550602040leaf: 25, 30 → found (2 nodes read)
51025303335overflow!50602040m = 4: at most 3 keys per node 510253035506020334025 30 | 33 ↑ | 35
Figure 10.40: Search and insertion, order 4. (a) Searching for 30: 20 < 30 < 40, go to the middle child. (b) Found in the leaf; one node read per level. (c) 33 takes the same path into the leaf, which now has 4 keys, over its capacity of m − 1 = 3. (d) The split sends 33 up, leaving [25, 30] and [35]; the root is now [20, 33, 40].

The four-key leaf [25, 30, 33, 35] has no single median. The slides pick the right middle key, 33, leaving two keys on the left and one on the right; the left one would also do, as long as the choice is consistent. Odd orders avoid the issue: with m = 3 an overflowing node has three keys and one median.

Splitting means a B-tree grows from the bottom up. A BST grows downward as leaves are added, but a B-tree gains height only when its root splits, pushing all leaves down at once, so they always stay at the same depth.

Deletion reverses insertion. A node that falls below minimum occupancy borrows a key from a sibling or merges with it; a merge takes a key from the parent, and the shortage may propagate upward. If the root loses its last key, it is removed and the height drops by one.

10.7.3Insertion and splitting, step by step

Insert 10, 20, 30, 40, 50, 60, 70 into an empty B-tree of order m = 3, where a node holds at most 2 keys and 3 children and splits on reaching 3 keys.

Table 10.24: Inserting 10, 20, 30, 40, 50, 60, 70 into a B-tree with m = 3. Trees are written as “root{children}.”
InsertEventResult
10added to the root leaf; 1 key[10]
20added to the root leaf; 2 keys[10, 20]
30[10, 20, 30] overflows; the median 20 moves up into a new root (root split, height 1)[20]{[10] [30]}
40added to the right leaf[20]{[10] [30, 40]}
50[30, 40, 50] overflows; 40 moves up to the parent, which has room[20, 40]{[10] [30] [50]}
60added to the right leaf[20, 40]{[10] [30] [50, 60]}
70[50, 60, 70] overflows; 60 goes up; the parent [20, 40, 60] overflows; 40 becomes the new root (root split, height 2)[40]{[20]{[10] [30]} [60]{[50] [70]}}

The 7 keys occupy 3 levels; every internal node has 2 children and all leaves share a depth. The height grew only at the two root splits. Sorted input that would turn a plain BST into a chain yields a perfectly balanced B-tree.

102030overflow! 103020
10304050overflow!20 1030502040
10305070204060overflow! 10305070206040
Figure 10.41: Splits. (a) 30 makes [10, 20, 30], over capacity. (b) Root split: 20 is the new root. (c) 50 overflows [30, 40, 50]. (d) 40 moves up; the split stops there. (e) 70 sends 60 up, overflowing the parent [20, 40, 60]. (f) Root split: 40 is the new root; one level taller, all leaves at the same depth.
Common mistake

Adding a new leaf below an overflowing one. A B-tree grows sideways: a split turns the full node into two siblings and sends the median up, and only a root split adds height. Nodes below the leaves would break the equal-depth rule.

10.7.4The B+ tree

The B+ tree, the variant most used in databases, differs from the B-tree in three ways:

  • Data in the leaves: All keys and records live in the leaves; internal nodes hold only routing keys, copies of leaf keys.
  • Linked leaves: The leaves form a left-to-right linked list, so range queries like “20 to 50” walk along it.
  • Wider internal nodes: Without records, more keys fit in a block, so the order rises and the tree gets shallower.
2040510202530405060leaves form a linked list: a range query reads left to right
Figure 10.42: A B+ tree: routing keys 20 and 40 at the root; leaves [5, 10], [20, 25, 30], [40, 50, 60], linked for range queries. 20 and 40 appear at the root and in the leaves.

Every search ends at a leaf, where the records are; there is no B-tree-style early exit at an internal node. In return, every search descends the same number of levels, so its cost is predictable. Most relational database systems use B+ trees for table indexes.

10.7.5Range queries in a B+ tree

Find the keys from 10 to 45 in the tree of Figure 10.42.

  1. At the root, 10 < 20, so the leaf holding the lower bound is the leftmost child. This descent is a search, O(log n) levels.
  2. Leaf [5, 10]: skip 5, take 10, then follow the link to the next leaf without going back up.
  3. Leaf [20, 25, 30]: take all three and follow the link.
  4. Third leaf [40, 50, 60]: take 40; stop at 50 > 45.

The result 10 20 25 30 40 is sorted. The cost is one descent plus the leaves read: O(log n + k) for k results. In a B-tree, with keys spread over internal nodes too, the query would climb up and down repeatedly; the leaf chain avoids that.

2040510202530405060query: 10 ≤ key ≤ 45result: 10 2040510202530405060query: 10 ≤ key ≤ 45result: 10 20 25 30
2040510202530405060query: 10 ≤ key ≤ 45result: 10 20 25 30 40 2040510202530405060query: 10 ≤ key ≤ 45result: 10 20 25 30 40
Figure 10.43: Range query 10 to 45 in a B+ tree. (a) First leaf: 10. (b) Second leaf: 20, 25, 30. (c) Third leaf: 40; stop at 50 > 45. (d) Result 10 20 25 30 40, cost O(log n + k).
Example 10.8

A registration system stores 2 million student records in a B+ tree of order 100, keyed by ID. Internal nodes are at least half full, so each has at least 50 children, and the height is at most about ⌈log50(2 · 106)⌉: since 503 = 125,000 and 504 = 6,250,000, at most 4 levels. Finding a student reads at most 4 blocks; listing the 150 students with IDs 2020001 to 2020150 takes one descent and a few leaf reads. A balanced binary tree would have height at least 20.

10.8Common mistakes

Most bugs in search tree code are of a few types. Those noted throughout the chapter are collected here as a checklist for reviewing tree code.

Table 10.25: Common mistakes in search tree code.
MistakeWhat happensCorrect approach
Ignoring the return valuedelete(root, 40); breaks the tree when the root changes.root = delete(root, 40);, root = insert(root, 5);
Checking only childrenNon-BSTs pass a parent–child check.Carry a (min, max) interval down from the root (isBST).
Comparing objects with ==References are compared; a key in the tree is not found.compareTo for order, equals or compareTo(...) == 0 for equality.
Height update orderWrong balance factors after an AVL rotation, needless rotations.Update the node that moved down first, then the new root.
“A BST is always O(log n)”On sorted input h = n − 1 and operations are O(n).O(log n) is guaranteed only in balanced trees (AVL, red-black).
Forgetting null childrenNullPointerException, or wrong balance and color decisions.Empty subtrees have height −1 and NIL is black; helpers like h(null) and isRed(null) encode this.
Single instead of double rotationIn LR or RL the tree tips the other way, still unbalanced.Check the child's balance factor; in a zigzag, rotate the child first.
Zig-zig orderThe splay tree loses its amortized guarantee.Rotate the grandparent first, then the parent.

Two misconceptions lie behind these mistakes. One is seeing the tree only locally, a node with its parent and children, when the BST property, the AVL condition and the black-height rule all constrain whole subtrees. The other is forgetting Java's reference semantics: tree methods return a subtree's new root, and nothing changes until the caller stores it in a link or variable.

10.9Summary

  • A search tree keeps elements ordered by key; search, insertion and deletion follow one path from the root in O(h), where h ranges from about log2 n to n − 1.
  • The BST property: all keys in a node's left subtree are smaller and all in its right subtree larger, for whole subtrees. Inorder traversal gives sorted output in O(n). The minimum is leftmost, the maximum rightmost.
  • Insertion descends as in a search and attaches a leaf at the empty link reached. Methods return the subtree's new root: root = insert(root, x).
  • The successor of x is the smallest larger key, the predecessor the largest smaller one; ceiling and floor also accept equality. All take O(h).
  • Deletion has three cases: remove a leaf; bypass a one-child node; replace a two-child node's value with its successor's (or predecessor's) and delete that node, which, having no left child, is an easy case.
  • A range query with pruning takes O(h + s). BST validation carries an allowed interval down from the root and takes O(n).
  • A plain BST's shape follows insertion order (sorted input gives a chain; random order, average depth about 1.39 log2 n). Balanced trees guarantee h = O(log n) by small local changes.
  • An AVL tree has bf = h(left) − h(right) ∈ {−1, 0, 1} at every node. The sparsest AVL tree grows like the Fibonacci numbers, so h < 1.44 log2(n + 2). LL and RR need one rotation, LR and RL two; a rotation is O(1) and preserves the inorder sequence.
  • A red-black tree has a black root, no adjacent reds, and equal black counts on all paths, so the longest path is at most twice the shortest and h ≤ 2 log2(n + 1). Insertion fix-up depends on the uncle: red means recolor, black means at most two rotations.
  • A splay tree moves each accessed node to the root by zig, zig-zig and zig-zag steps: O(n) worst case per operation, O(log n) amortized.
  • A B-tree node holds at most m − 1 keys and m children, and all leaves share a depth. Overflowing nodes split, sending the median up, so the tree grows from the bottom. A B+ tree keeps data only in linked leaves; range queries take O(log n + k).
Table 10.26: Operation costs. (*) Amortized; one operation can be O(n). Inorder traversal is always O(n) and sorted.
StructureHeightSearch / insert / deleteHow is it balanced?
BSTlog n … nO(h)not balanced; worst case O(n)
AVL≤ 1.44 log nO(log n)bf ∈ {−1, 0, 1}; LL, RR, LR, RL rotations
Red-black≤ 2 log(n + 1)O(log n)recoloring + at most 2–3 rotations
SplayunboundedO(log n)*accessed node moved to the root by zig / zig-zig / zig-zag
B-tree / B+≈ logm nO(log n)overflowing nodes split; all leaves at the same depth

10.10Key terms

Each term links to the course glossary with its Turkish equivalent.

10.11Test Yourself

  1. Insert 35, 18, 52, 10, 25, 44, 60, 22, 48 into an empty BST. What is its height and its inorder traversal? How many nodes does search(root, 48) compare?

    Answer

    The tree is 35(18(10, 25(22, —)), 52(44(—, 48), 60)). The deepest nodes, 22 and 48, are at depth 3, so the height is 3. Inorder: 10 18 22 25 35 44 48 52 60. Searching for 48 compares 35 (right), 52 (left), 44 (right) and 48: 4 nodes, the depth of 48 plus one.

  2. Delete 18 and then 35 from the same tree, using the inorder successor for two-child nodes. What is the tree after each deletion?

    Answer

    The successor of 18 is 22 (right to 25, then left); 22 is copied into the node of 18 and the old leaf deleted: 35(22(10, 25), 52(44(—, 48), 60)). The successor of 35, left from 52, is 44; it is copied to the root and the old 44, having one child, is bypassed: 44(22(10, 25), 52(48, 60)).

  3. In the tree of question 1, what are successor(25), predecessor(44), ceiling(45) and floor(21)?

    Answer

    successor(25) = 35 and predecessor(44) = 35: 25 has no right subtree and 44 no left subtree, and in both cases the nearest qualifying ancestor is the root. ceiling(45) = 48 and floor(21) = 18; 45 and 21 are not in the tree.

  4. In the tree of question 1, what does range(root, 20, 46) print? Which nodes are never visited?

    Answer

    22 25 35 44, visiting 35, 18, 25, 22, 52, 44, 48. At 18, 20 < 18 is false, pruning 10; at 52, 52 < 46 is false, pruning 60. 48 is visited (right of 44, since 44 < 46) but is out of range. 10 and 60 are never visited.

  5. Insert 30, 20, 10, 40, 50, 35 into an empty AVL tree. Which insertions trigger which case, and what is the final tree?

    Answer

    10: bf(30) = 2, bf(20) = 1 → LL; right at 30 gives 20(10, 30). 40: balanced. 50: bf(30) = −2, bf(40) = −1 → RR; left at 30 gives 20(10, 40(30, 50)). 35 lands right of 30: bf(20) = −2, bf(40) = 1 → RL; right at 40, then left at 20. Final tree 30(20(10, —), 40(35, 50)), height 2.

  6. Insert 30, 20, 10, 25, 27 into an empty red-black tree. Which case applies at each insertion needing a fix-up? Give the final tree with colors.

    Answer

    10: red parent 20, NIL uncle, a line → right at 30; 20B(10R, 30R). 25: red parent 30, red uncle 10 → recolor (10, 30 black, 20 red), and the root 20 turns black again. 27: red parent 25, NIL uncle, a triangle (27 right, 25 left child) → left at 25, right at 30; 27 black, 30 red. Final tree 20B(10B, 27B(25R, 30R)), 2 blacks on every path.

  7. Given the splay tree 50(30(20(10, —), 40), 70), which step does splay(40) use, and what is the result?

    Answer

    The path is 50 → 30 → 40 (p = 30, g = 50); 40 is a right child, 30 a left child: zig-zag. Rotating left at 30 and right at 50 gives 40(30(20(10, —), —), 50(—, 70)) in one step.

  8. Insert 5, 15, 25, 35, 45 into an empty B-tree of order 3. Which insertions cause splits, and what is the final tree?

    Answer

    25 overflows [5, 15, 25], and 15 moves up into a new root: [15]{[5] [25]}. 35 joins the right leaf: [25, 35]. 45 overflows [25, 35, 45]; 35 moves up to the root, which has room: [15, 35]{[5] [25] [45]}. Two splits, one at the root; height 1.

  9. In a red-black tree whose root has black height 3, how many nodes can the shortest and longest root-to-NIL paths contain? What is the minimum number of nodes in such a tree?

    Answer

    Every path has 3 black nodes. The shortest is all black: 3 nodes. On the longest, a red can follow each black: at most 6 (black, red, black, red, black, red, as the root is black). The minimum is 23 − 1 = 7 nodes: the all-black perfect tree of height 2.

  10. In the B+ tree of Figure 10.42, which keys lie between 22 and 55, and how many leaves are read?

    Answer

    At the root, 20 ≤ 22 < 40 leads to the middle leaf [20, 25, 30]: skip 20, take 25 and 30. The link leads to [40, 50, 60]: take 40 and 50, stop at 60 > 55. Result 25 30 40 50; 2 leaves read, with no return to the root.

10.12Further reading

  • M. T. Goodrich, R. Tamassia, and M. H. Goldwasser, Data Structures and Algorithms in Java (6th ed., 2014), Chapter 11: covers binary search trees, AVL, splay, (2, 4) and red-black trees in the same order, with Java code close to that of these notes.
  • R. Sedgewick and K. Wayne, Algorithms (4th ed., 2011), Sections 3.2 and 3.3: presents BSTs and left-leaning red-black trees by relating them to 2-3 trees; the easiest way to see why red-black trees work.
  • T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein, Introduction to Algorithms (4th ed., 2022), Chapters 12, 13 and 18: BSTs, red-black trees (with all cases, including deletion) and B-trees, with proofs.
  • D. D. Sleator and R. E. Tarjan, “Self-Adjusting Binary Search Trees” (1985): the paper that introduced splay trees, proving the amortized O(log n) bound with a potential function.

10.13References

  1. G. M. Adelson-Velsky, E. M. Landis, “An algorithm for the organization of information”, Soviet Mathematics Doklady, 3, 1259–1263, 1962.
  2. R. Bayer, E. M. McCreight, “Organization and maintenance of large ordered indexes”, Acta Informatica, 1(3), 173–189, 1972.
  3. T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein, Introduction to Algorithms, 4th ed., MIT Press, 2022.
  4. M. T. Goodrich, R. Tamassia, M. H. Goldwasser, Data Structures and Algorithms in Java, 6th ed., Wiley, 2014.
  5. L. J. Guibas, R. Sedgewick, “A dichromatic framework for balanced trees”, 19th Annual Symposium on Foundations of Computer Science, 8–21, 1978.
  6. R. Sedgewick, K. Wayne, Algorithms, 4th ed., Addison-Wesley, 2011.
  7. D. D. Sleator, R. E. Tarjan, “Self-adjusting binary search trees”, Journal of the ACM, 32(3), 652–686, 1985.

Related pages: Chapter 10 slides · Study questions · Glossary · © Sercan Külcü. All rights reserved.