التوازن (مفهوم فقط)
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
In Lesson 2 you learned that a BST search costs about one step per level, so it is fast when the tree is short and wide. This lesson looks at the other side of that claim. The shape of a BST depends on the order in which values arrive, and some orders produce a tree that is as slow as a plain list.
You will see the problem in a small example (inserting 1, 2, 3, 4, 5), measure it in code with 1,000 values, and learn the fix at a conceptual level: self-balancing trees repair the shape as values are inserted. You will not implement one. This is a concept lesson: the aim is to know the problem, the idea of the cure, and where Java already provides it.
Why this matters: data that arrives in order is normal in backend systems (ids that count up, timestamps, imported files sorted by name). If you pick a plain BST for such data, you lose exactly the speed you chose it for. Knowing why lets you pick TreeMap with confidence.
This lesson builds on Lesson 2 (BST insert and search), Lesson 1 (height), and Big O notation from the complexity module.
Learning Objectives
By the end of this lesson you will be able to:
- Show that inserting sorted values into a plain BST produces a chain.
- Explain why a chain has height n - 1 and makes search O(n).
- Contrast the best shape (height about log n) with the worst (height n - 1).
- Describe what a rotation does and what it preserves.
- Name the idea of a self-balancing tree and give two examples of one (AVL, red-black).
- Say which JDK collections are backed by a balanced tree.
Written Explanation
The same values, two shapes
Insert 1, 2, 3, 4, 5 into an empty BST, in that order. Each new value is larger than everything before it, so it always goes to the right of the last node:
1
\
2
\
3
\
4
\
5This is a chain, sometimes called a degenerate tree. It still obeys the BST rule, and in-order traversal still gives 1 to 5. But it has height 4: five nodes, one per level. Now insert the same values in the order 3, 2, 4, 1, 5:
3
/ \
2 4
/ \
1 5Height 2. Same values, same rule, a very different shape. The number of steps to find 5 is 5 in the chain and 3 in the bushy tree.
Why the chain is slow
Search and insert move one level down per step, so their cost is bounded by the height. For a tree of n nodes:
| Shape | Height | Search and insert |
|---|---|---|
| Best: full and balanced | about log2 n | O(log n) |
| Worst: a chain | n - 1 | O(n) |
A chain is no better than scanning a linked list. Worse, building a chain of n values costs about n(n-1)/2 comparisons in total, because the k-th insert walks past all k-1 nodes already there. The OpenDSA textbook makes the same point: sorted insertion gives height n - 1 and a total cost that grows with the square of n.
The reverse order (5, 4, 3, 2, 1) gives a chain going left. Any strictly increasing or decreasing sequence is a worst case, and nearly sorted input is almost as bad.
Measuring it
We insert 1,000 values twice, once in sorted order and once shuffled with a fixed seed (new Random(42)), and compare the height and the number of nodes visited when searching for 1000:
n=1000 sorted : height 999, steps to find 1000: 1000
n=1000 shuffled : height 26, steps to find 1000: 8(Compiled and run with JDK 21. The shuffled numbers depend on the seed and the exact shuffle, so another run with another seed would show somewhat different numbers.) The sorted tree needs a thousand steps. The shuffled tree is not perfect either: a perfectly balanced tree of 1,000 nodes would have height 9, and ours has 26. But it is far from 999. A plain BST works well on data that arrives in random order and badly on data that arrives in order. You usually do not control arrival order, so a structure that does not depend on it is valuable.
The cure: keep the tree balanced
A balanced tree keeps its height close to log2 n whatever the insertion order. Self-balancing trees do this automatically. After an insert or a delete, the tree checks whether some part has become too tall on one side, and if so repairs it with one or more rotations.
A rotation is a small local rearrangement. Take the chain 1, 2, 3 (root 1, right child 2, whose right child is 3). A left rotation at the root makes 2 the new root, with 1 on its left and 3 on its right:
1 2
\ / \
2 -> 1 3
\
3Height drops from 2 to 1. And the rotation keeps the BST rule: the in-order output is still 1, 2, 3. A rotation changes the shape only, never the order, so searches stay correct. The code is four lines:
static Node rotateLeft(Node x) {
Node y = x.right; // y becomes the new root of this subtree
x.right = y.left; // y's old left subtree moves under x
y.left = x;
return y; // the caller stores y where x used to be
}A self-balancing tree decides when to rotate by following a rule. Two famous families:
- AVL trees keep the heights of the two subtrees of every node within one of each other.
- Red-black trees color nodes red or black and follow color rules that keep the longest path at most about twice the shortest.
Both guarantee a height of order log n, so search, insert, and delete are O(log n) in the worst case. The price is a little extra work per insert and more code. You do not need the exact rules, only the idea: the tree repairs itself so that no input order can make it tall.
What Java gives you
The JDK already contains this. The Oracle documentation says TreeMap is a red-black tree implementation of NavigableMap, with guaranteed log(n) time for containsKey, get, put, and remove, and entries kept sorted by key. TreeSet is the set version, backed by a TreeMap. In practice you write new TreeMap<>(), insert in any order, and never think about rotations. That is why this lesson is concept-only: your own BST from Lesson 2 teaches how the idea works, while TreeMap is what you use in an application.
Examples
Example 1: five values, two shapes (simple)
Using the insert and height methods from the earlier lessons, we build two trees from the same five values and count the nodes visited when searching for 5:
Node chain = build(List.of(1, 2, 3, 4, 5));
Node bushy = build(List.of(3, 2, 4, 1, 5));
System.out.println("1..5 sorted : height " + height(chain) + ", steps to find 5: " + steps(chain, 5));
System.out.println("3,2,4,1,5 : height " + height(bushy) + ", steps to find 5: " + steps(bushy, 5));Here build inserts the values one by one into an empty tree, and steps is the same loop as trace in Lesson 2 but counts the nodes it compares. Output (compiled and run with JDK 21):
1..5 sorted : height 4, steps to find 5: 5
3,2,4,1,5 : height 2, steps to find 5: 3With five values the gap is small; at 1,000 values it was 1,000 steps versus 8.
Example 2: a rotation fixes a chain (practical)
We build the chain 1, 2, 3, apply rotateLeft to its root, and check the result with an in-order traversal.
Node r = Balance.build(List.of(1, 2, 3));
System.out.println("before height " + Balance.height(r));
r = rotateLeft(r);
// then print the root, its children, the new height, and the in-order listOutput (compiled and run with JDK 21):
before height 2
after root 2 left 1 right 3 height 1 in [1, 2, 3]The height fell from 2 to 1 and the in-order list is unchanged. A self-balancing tree performs this step automatically, in more cases (right rotations, and double rotations when the shape bends the other way). Deciding when to rotate is the hard part, which is why you rely on TreeMap.
Example 3: sorted data in a real tree class, and the stack (realistic)
The recursive insert from Lesson 2 has a second problem on chains: each insert recurses to the bottom of the chain, so the call stack gets as deep as the tree. We insert 1, 2, 3, and so on in order, for four different sizes, and catch StackOverflowError:
for (int size : new int[] {5_000, 10_000, 50_000, 100_000}) {
try {
Node root = null;
for (int i = 1; i <= size; i++) root = insert(root, i);
System.out.println(size + " sorted inserts: ok, height " + height(root));
} catch (StackOverflowError e) {
System.out.println(size + " sorted inserts: StackOverflowError");
}
}Output from one run on JDK 21 with default settings:
5000 sorted inserts: ok, height 4999
10000 sorted inserts: ok, height 9999
50000 sorted inserts: StackOverflowError
100000 sorted inserts: StackOverflowErrorThe size at which this fails depends on the JVM, the machine, and the stack size, so do not treat these numbers as limits. The pattern is the point: a chain turns "log n levels of recursion" into "n levels". TreeMap has no such problem, because it keeps its height small.
Backend Insight
- Use
TreeMaporTreeSetwhen you need sorted keys. Typical uses are range queries ("all events between two timestamps") and ordered reports. You pay O(log n) per operation and get ordering. If you do not need ordering, aHashMapis typically the better default. - Ordered input is the norm. Auto-increment ids, creation timestamps, and files sorted by name are all sorted data. A hand-written, unbalanced BST keyed by such values is a classic performance bug that passes every small test and fails in production.
- Databases face the same problem. A database index is a tree that must stay shallow while millions of rows arrive, often in key order. The B-tree used by many databases (a later module covers it) is a balanced tree with many children per node, which keeps the height very small. The balancing idea of this lesson carries over.
Common Mistakes
Mistake 1: Assuming "BST" always means O(log n)
The O(log n) claim holds for a balanced tree. A plain BST has O(log n) height only if the insertion order is kind to it. In the worst case it is O(n).
Mistake 2: Thinking a balanced tree is a sorted array
Balanced does not mean sorted in a line. It means short. A balanced BST still has the left-smaller, right-larger structure, and in-order gives the sorted listing.
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. Insert 10, 20, 30, 40 into a plain BST. Draw it, give its height, and say how many nodes you visit to find 40.
أظهر الحل A1
A1. The tree is a chain: 10, with 20 as its right child, then 30, then 40. Height is 3 (four nodes, three links). Finding 40 visits four nodes: 10, 20, 30, 40.
A2. Give an insertion order of 1, 2, 3 that produces a tree of height 1, and another that produces height 2.
أظهر الحل A2
A2. Height 1: insert 2 first, then 1 and 3 in either order (2, 1, 3 or 2, 3, 1). Height 2: any order that starts with 1 or 3, such as 1, 2, 3 or 3, 1, 2.
A3. Name two JDK classes that are backed by a balanced tree, and one that is not.
أظهر الحل A3
A3. TreeMap and TreeSet use a red-black tree. HashMap does not: it uses a hash table (the next module covers hashing in detail).
Level B: Application
B1. Why does inserting 1, 2, ..., n in order cost about n(n-1)/2 comparisons in total? Compute the number for n = 1,000.
أظهر الحل B1
B1. The first value needs no comparison. The second is compared with 1 node, the third with 2, and the k-th with k - 1, because each new value passes the whole chain. The sum is 0 + 1 + ... + (n - 1) = n(n - 1)/2. For n = 1,000 that is 499,500 comparisons.
B2. A teammate stores order records in a plain BST keyed by an auto-increment order number, inserted as orders arrive. Explain the risk in two sentences.
أظهر الحل B2
B2. Order numbers only increase, so every new record goes at the far right and the tree becomes a chain. Lookups and inserts become O(n) and get slower the more orders there are (and a deep recursion could overflow the stack). A TreeMap or a database index avoids this.
B3. A balanced tree and a chain hold the same 15 values. Roughly how many steps does a worst-case search take in each?
أظهر الحل B3
B3. For 15 values, a perfectly balanced tree has height 3, so a worst-case search visits at most 4 nodes. A chain of 15 has height 14, so a worst-case search visits 15 nodes.
Level C: Challenge
C1. Draw the chain 5, 6, 7 (each one the right child of the previous). Apply one left rotation at the root and draw the result. Check the in-order output before and after.
أظهر الحل C1
C1. Before: 5, right child 6, whose right child is 7. After: root 6, left child 5, right child 7. The in-order output is 5, 6, 7 in both cases, and the height drops from 2 to 1.
C2. The chain 3, 2, 1 (each one the left child of the previous) needs the mirror image of a left rotation. Write rotateRight(Node x). Hint: swap the roles of left and right in rotateLeft.
أظهر الحل C2
C2.
static Node rotateRight(Node x) {
Node y = x.left; // y becomes the new root of this subtree
x.left = y.right; // y's old right subtree moves under x
y.right = x;
return y;
}Applied to the chain 3 (left child 2, whose left child is 1), it returns 2 with children 1 and 3.
Mini Challenge
Run your own experiment from the BST code you wrote in Lesson 2. Write a method height (from Lesson 1) and a loop that inserts 1 to n in order, for n = 100, 200, 400. Print the height each time. Then insert the same values shuffled (use Collections.shuffle with a fixed seed so your results can be repeated) and print the heights again.
Check your work: the sorted heights should be n - 1 for every n, the shuffled heights should be far smaller, and doubling n should increase the shuffled height only slightly.
Summary & Next Lesson
Key takeaways
- A plain BST's shape depends on insertion order. Sorted input produces a chain of height n - 1.
- A chain makes search and insert O(n), and building it costs about n(n-1)/2 comparisons.
- A balanced tree keeps its height near log n. Self-balancing trees (AVL, red-black) do this with rotations after inserts and deletes.
- A rotation changes the shape but not the in-order sequence.
- In Java,
TreeMapandTreeSetare backed by a red-black tree, so you get O(log n) operations without writing balancing code.
Next lesson
The next lesson, "Heaps and PriorityQueue", introduces a different tree-shaped structure that is not a search tree at all. It answers one question very fast: "which item is the smallest, or the most urgent?"
Self-check
- In what order of insertion does a plain BST become a chain?
- What is the height of a chain of n nodes?
- What does a rotation preserve?
- Which JDK class gives you a sorted map backed by a balanced tree?
- Why does a self-balancing BST stay fast on sorted input?
أظهر الإجابات
- Strictly increasing or strictly decreasing order, such as 1, 2, 3, 4, 5.
- n - 1.
- The in-order sequence, so the BST rule still holds.
TreeMap(andTreeSetfor a set).- It repairs its shape with rotations during inserts, so its height stays near log n whatever the input order.
References
- OpenDSA (Virginia Tech): Binary Search Trees. It says that inserting keys in sorted order gives a chain of height n - 1 with Theta(n) per operation and Theta(n squared) to build, that a balanced tree has height about log n, and that AVL trees, splay trees, and 2-3 trees are self-balancing variants.
- Oracle Java SE 21 API: TreeMap. It says
TreeMapis a red-black tree implementation with guaranteed log(n) cost forcontainsKey,get,put, andremove. - I did not fetch a source for the AVL and red-black balance rules or for
TreeSetbeing backed byTreeMap; those statements are standard textbook and JDK facts given here at a conceptual level, so check them in the Javadoc before relying on them. - Every Java example above was compiled and run with JDK 21 and the output is copied from the real run.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.