Hashing and Binary Search Trees
Prerequisite Knowledge
This lecture builds on the following concepts from earlier lectures. If any feel unfamiliar, review the linked notes before proceeding.
Previously Covered in This Subject
- Stacks and the stock span problem — covered in Lecture 4 (Abstract Data Types: Stacks, Queues, Lists, and Vectors)
- Queues and circular arrays — the wrap-around layout behind the queue size formula — covered in Lecture 4
- Dynamic arrays and doubling on growth — the same growth rule the hash table borrows for rehashing — covered in Lecture 4
- Trees and binary trees — root, internal and external nodes, depth, height, full and complete trees — covered in Lecture 5 (Trees and Heaps)
- Tree traversals (inorder, preorder, postorder) and reconstructing a tree from two traversals — covered in Lecture 5
- Heaps — the heap property, array representation, insertion and upheap — covered in Lecture 5
7.1 Good Hash Functions and the Load Factor
7.1.1 What a Good Hash Function Buys
Hook: A hash table is advertised as the structure that finds anything in one step. But that promise comes true only if the hash function itself behaves well — so what exactly does a "good" hash function do, and what happens when it fails?
We last built a dictionary on top of a hash table, and we saw that multiple elements can carry the same key. When two keys land in the same bucket we call that a collision (two different keys forced into the same table cell), and to handle collisions we studied separate chaining — each bucket holds a linked list, and colliding elements are appended to the list attached to that bucket cell. Separate chaining solved the handling problem, but it leaves a question open: if we can stop collisions from happening so often, we barely need the handling machinery at all. That is why a good hash function matters.
Intuition + analogy: Think of a hash table as a coat-check counter with numbered hooks. The hash function decides which hook a coat goes to. If the checker always sent every coat to hook 1, you would wait behind a long line every time you came back; if the checker spread coats evenly across all hooks, you would walk straight to your coat and leave. The relationship is exact: hash function = the coat-checker's rule, bucket = one hook, collision = two coats on one hook. The analogy breaks only in one place: a coat-checker can hang two coats on one hook (that is separate chaining), whereas the whole point of a good hash function is to make that happen so rarely that the hook almost always holds one coat or none.
A good hash function tries to minimize collisions as much as possible. Think about what that means for the bucket array: if collisions are rare, most of our buckets are either empty or hold just a single entry. The class was asked to put the idea into words, and the answer that landed was "equal distribution as much as possible" — the hash function should spread keys evenly over the buckets, not pile them into one corner of the table.
Why does even distribution translate into speed? If every bucket holds at most one entry, retrieving a particular element means hashing its key, walking to that one bucket, and comparing a single element. The time is constant — independent of how many elements the table holds. Retrieval becomes (read "order one": the work stays the same no matter how big the table gets), which is the ideal case and the entire reason we reach for a hash table in the first place.
The flip side was shown with a small example. If we let a bad hash function pile everything into bucket 1, one bucket grows into a long linked list, and retrieving an element from that bucket means walking the list. The dictionary then degrades to the performance of a linked list and loses the advantage of that single-lookup retrieval. A good hash function is what keeps the "one lookup" promise alive.
7.1.2 The Load Factor
A good hash function minimizes collisions, but even with a good function the table fills up over time. We need a number that tells us how full the table is allowed to get. That number is the load factor.
Assume we use a good hash function to index entries in a bucket array of capacity . We expect each bucket to hold about elements. This ratio has a name and a symbol:
Formalize. The load factor (a measure of how full the hash table is), written (the Greek letter alpha), is the number of entries divided by the number of cells:
- (the number of entries) — how many key-value pairs are actually stored in the table right now. Example: 60 entries.
- (the capacity) — how many buckets (cells) the bucket array was created with. Example: cells.
- (the load factor) — the fraction of the table that is occupied. For 60 entries in 100 cells, , meaning the table is 60% full.
The average bucket holds elements, because the entries are spread over buckets as evenly as the hash function can manage.
The load factor should be bounded by a small constant, preferably below 1. A load factor of 1 means the table is 100% full, which is not ideal; a load factor above 1 means the table has more entries than cells, which guarantees collisions. The session's standard choice is 0.75: keep at all times.
In plain terms, the load factor is a measure of how full the hash table is allowed to get before its capacity has to be increased. When the table reaches the load factor, we grow the array — normally by doubling its size, exactly the trick we used with the dynamic array. Doubling restores the load factor to about half of the chosen bound, so the table never sits near-full for long.
7.1.3 Worked Example — How Full before Expanding
The instructor asked: if the load factor is 0.75, what does that mean? The answer: we are allowing the hash table to get 75% full — 75% of its capacity is allowed to fill before we expand.
Worked example. Suppose the capacity of the hash table is 16 (a random number taken just for the example) and the load factor is 0.75. How many elements can we store before the table grows?
Step 1 — State what is known. cells, bound .
Step 2 — Translate the bound into a count. The largest allowed number of entries is the product of capacity and load factor:
Step 3 — Read the result. We may store 12 elements before the table must grow. After storing the 12th element, the capacity is increased — in ideal (and real) cases we double the size, so the new capacity becomes , and the load factor drops back to , well below the bound.
Sense-check. 0.75 means "three quarters full", and three quarters of 16 is — the numbers agree, so the answer is consistent.
The same reasoning carries to the load factor choice: when the load factor is reached, we double the capacity of the hash table, change the compression map to match the new size, and rehash the existing elements (the next section develops these two steps in detail). The load factor stays bounded below the constant we chose.
7.1.4 Assumptions and Scope
Assumption: The whole argument so far assumes a good hash function — one that spreads keys evenly over the buckets. The load factor formula only predicts an average bucket size of when that spread holds. If the hash function is bad (for example, everything lands in one bucket), the actual table behaves nothing like the average prediction. Scope: is a policy choice for keeping operations fast, not a law of nature. A table works (inserts and finds stay correct) above 0.75 — but collision handling must do more and more work, so lookups get slower. The load factor bound is what protects speed, and the growth-and-rehash cycle is what enforces it. Note that the formula assumes for open addressing (one item per cell); values above 1 are possible only with chaining, where lists share cells.
7.1.5 Visual Intuition — What the Table Looks Like as It Fills
Picture a bar chart of the 16 buckets of the example, with the bucket index 0 to 15 on the horizontal axis and the number of entries stored in that bucket on the vertical axis. With a good hash function the bars look almost level: most bars are 0 or 1 tall, and no bar rises far above the rest. The average bar height is exactly the load factor . The landmark to watch is the point where reaches : at that moment the chart is "full enough", and the growth rule replaces the whole chart with a wider one (32 bars), so every bar height roughly halves. The takeaway: the picture of a healthy hash table is a flat, low skyline — the moment tall bars appear, the hash function or the load factor is failing you.
7.1.6 Common Pitfalls
- Forgetting the load factor is a ratio, not a count. says nothing by itself; it must be multiplied by the capacity to get a usable limit. "Load factor 0.75 with capacity 100" means 75 entries, not 0.75 entries.
- Believing a good hash function eliminates collisions. It only makes them rare. Even a perfect spread guarantees that once passes 1, collisions are unavoidable (more entries than cells); and any hash function can be defeated by data chosen to collide.
- Letting the load factor climb without bound. If you keep inserting without resizing, keeps growing, the buckets keep growing, and the "constant time" promise silently decays toward the speed of a linked list. The load factor bound is what prevents this.
- Ignoring the doubling-and-rehash consequence. Growing the array is not free: you must also re-insert every existing element under the new size (Section 7.3). Students who double the size but forget the rehash end up with a table where lookups go to the wrong cells.
7.1.7 Student Questions and Answers
Q: If I say the load factor is 0.75, what does that mean? A: The load factor is a measure of how full the hash table is allowed to get before its capacity is automatically increased. A load factor of 0.75 means we allow the hash table to get 75% full before we expand the table.
Q: If the capacity of the hash table is 16 and the load factor is 0.75, how many elements can I store before the size increases? A: 16 into 0.75 is 12. So after storing the 12th element, the capacity is increased. Normally we do that by doubling the size — remember the dynamic array.
Q: With a good hash function, most buckets are empty or hold one entry. Is that really what we are aiming for? A: Yes. If you use a very good hash function it minimizes the collisions as much as possible, so most of the buckets in the bucket array will be empty or store just a single entry. That keeps retrieval time constant.
Exam note: the instructor stated explicitly that if a load factor is not given in a problem, assume it to be 0.75 — the standard default used through the session.
Recap + Bridge: A good hash function spreads keys evenly so that most buckets hold zero or one entry and retrieval stays ; the load factor sets the limit on fullness, and crossing it triggers doubling. Next we see why even this claim has to be stated carefully — it is an expected time that depends on the load factor staying bounded.
Real-world connection: every serious dictionary implementation uses exactly this design — the hash function is chosen to spread the key type evenly, the load factor is fixed at a small constant, and the table doubles (with rehashing) when that constant is hit. For example, Python's dictionaries and Java's HashMap both resize when the table crosses a load-factor threshold; the numbers differ (0.75 is common), but the mechanism is the same, and it is the reason a lookup in a dictionary of ten thousand entries takes about the same time as a lookup in one of ten.
7.2 Expected Running Time of Dictionary Operations
7.2.1 The Θ(α) Result and Its Assumption
It is tempting to say that a dictionary implemented with a hash table runs in time, full stop. The instructor warned against that habit: don't blindly say the dictionary running time is constant — the claim is about the expected (average) running time, and it rests on an assumption.
Hook: Every textbook and every course says "hash tables are O(1)". So why does the instructor insist the correct statement is longer and more careful? Because the constant-time claim has a hidden condition, and forgetting the condition turns a true statement into a wrong one.
The expected running time of the standard dictionary operations — find an element, insert an item, remove an element — in a hash-table dictionary is
Formalize. With a good hash function, the average cost of each dictionary operation is proportional to the average bucket length, which is the load factor:
- (theta) is the tight bound notation: the time grows like the expression inside, up to constant factors, so means "the time scales exactly with the load factor".
- is the load factor from Section 7.1.2 — the ratio of entries to capacity .
- Why the load factor, not ? Searching a bucket means walking its list; a good hash function gives each bucket about elements on average, and walking list entries costs time.
We say this is under the assumption that is upper bounded by — the number of entries is bounded by the capacity of the bucket array. When , the ratio is a constant, and the whole expression collapses to .
So the correct statement has two layers:
- Expected time: — it depends on how full the table is.
- Simplified claim: , valid only when the number of entries stays within the capacity ( for a constant ).
The instructor pressed this point with a live question: if every bucket in the bucket array stores a single entry, what is the time taken to retrieve a particular element? One voice answered ; the correct answer is — constant time, which is the ideal case, and which is the reason we use a hash table at all. The answer describes what happens when the buckets grow into long lists, which is exactly what a good hash function and a healthy load factor are supposed to prevent.
7.2.2 Why the Assumption Matters
The claim silently assumes the table grows as entries arrive. The mechanism that keeps bounded by is the load-factor-triggered resize: when the table reaches (say) 75% full, we double the capacity, which restores the load factor to a small constant. The next section develops this mechanism — the compression map and rehashing — because it is what turns the conditional claim into the unconditional "roughly constant" behavior users actually experience.
Intuition + analogy: A hash table with a fixed capacity is like a car park with a fixed number of spaces. If you promise "parking always takes the same short time" while cars keep arriving, that promise holds only while spaces remain. The moment the car park fills past its limit, drivers start circling (the probe/list walk grows), and the time is no longer constant. Resizing on the load factor is the rule "before the lot gets more than 75% full, build a bigger lot and move all the cars in" — that rule is what keeps the short-time promise honest.
7.2.3 Assumptions and Scope
Assumption: The result needs (a) a good hash function that spreads keys evenly, and (b) the load factor kept at a small constant, so that bucket lengths stay bounded. If either fails — a hash function that piles keys together, or a table that is never resized — the expected-time argument breaks. Scope: This is an expected (average-case) claim, not a worst-case guarantee. A specific unlucky set of keys can still collide even with a good hash function; the worst case for hashing is (developed in Section 7.11). The phrase "expected" is doing real work: the guarantee is about the average over keys, not about every key.
7.2.4 Visual Intuition — Time against Load Factor
Picture a graph with the load factor on the horizontal axis (from 0 to 1) and the average number of steps per operation on the vertical axis. With a good hash function and chaining, the curve is a gently rising straight line: gives about a quarter of a step per operation on average, about three quarters, about one step. The landmark is the vertical slice at : this is the chosen operating point, and the resize policy makes sure the table never drifts to the right of it for long. The one-sentence takeaway: keep the load factor on the left side of the graph and the expected cost stays a small constant.
7.2.5 Common Pitfalls
- Quoting "O(1)" without the conditions. The claim is expected time that becomes only when . On an exam, writing "dictionary operations run in O(1)" is the expected answer only in the setting of a bounded load factor; the instructor specifically warned against saying it blindly.
- Confusing the ideal answer with the degraded case. The live-question trap: one student answered for "every bucket holds a single entry". That answer describes the table where one bucket has grown into a long list — the very situation a good hash function and load factor prevent.
- Thinking the hash function itself must change when the table grows. The function stays the same; what changes is the compression map when the capacity appears in it (Section 7.3). A function like produces different values after changes even though the rule is unchanged.
- Forgetting that "expected" is not "guaranteed". A data set chosen to collide defeats any fixed hash function; the expected-time analysis averages over keys and assumes the hash behaves like a random spread.
7.2.6 Student Questions and Answers
Q: If every bucket in the bucket array stores a single entry, what is the time taken to retrieve a particular element? A: O(1) — constant time. That is the ideal case, and it is the reason we were using a hash function or hash table in the first place. (An answer of O(n) describes a table whose buckets have grown into long lists.)
Q: Do we need to modify the hash function when the size of the table increases? A: Not the function itself — but if the size appears as a factor inside it, the values change. If the hash function is , then when increases the computed values change, even though the function as such stays the same. Whenever we add elements and increase the size of the bucket array, we change the compression map to match the new size.
Q: Why do we take a prime number for the capacity ? A: We already demonstrated this with an example in the earlier session — a prime modulus spreads the hash values more evenly and avoids the collision patterns that composite capacities create. The reason is carried forward; it will matter again for quadratic probing and double hashing.
Recap + Bridge: The honest statement is expected time per dictionary operation, which behaves as exactly while the load factor is held at a small constant. That "held" is the key word — the mechanism that holds it is the compression map and rehashing, which we now develop.
Real-world connection: this conditional is why database and search-engine index structures report "expected constant time" lookups rather than flat guarantees, and why production hash tables (in standard libraries of languages like Python, Java, and Go) all ship with a built-in growth policy. The engineering lesson: the hash function and the growth policy are one system — a fast hash function with no resize discipline still collapses to linear-time behavior under enough data.
7.3 The Compression Map and Rehashing
7.3.1 Compression Mapping
Recall the two-step pipeline of a hash table. First the hash code mapping converts keys to integers. Then the compression mapping converts those integers into the range
— that is, into valid bucket indices. Every bucket array of capacity has cells numbered , so is the last (highest) valid index.
Formalize. A hash function is really two functions composed:
- Hash code mapping: key integer (for example, counting the characters in a word, or reading the characters as a number). This step knows nothing about the table.
- Compression mapping: integer bucket index in . The standard compression map is the modulus: .
The compression map is the part that depends on the capacity : its whole job is to bring integers into a range bounded by the current bucket array size. Hash code mapping and compression mapping together are what we loosely call "the hash table" when we talk about the bucket array size; always refers to the size of the bucket array.
7.3.2 Why Rehashing Is Necessary
Keeping the load factor below the chosen constant (say 0.75) requires growing the bucket array. But growing breaks the compression map — a map that produced indices in is now working with a new, larger . So whenever we increase the size of the bucket array, we must also change the compression mapping to match the new size.
That, in turn, forces a second step: we must insert all the existing hash-table elements into the new bucket array using the new compression mapping. The position of an element can change when changes, even though the element was already hashed and stored. Re-hashing those existing elements into the new array is called rehashing. It is not optional: when you increase the size of the bucket array, you have to rehash. A good choice for the new size is to double the size of the original array.
Intuition + analogy: Rehashing is like moving a library to a new building with twice the shelf space, and re-filing every book under a new shelving rule. The books are the same, but the rule for "which shelf does this title go on" depends on the number of shelves — so a title that sat on shelf 2 may now sit on shelf 8. Doubling the shelves without re-filing leaves every book in the wrong place, which is why the two steps (grow, re-fill) always come together.
Rehashing means two things, and the instructor stressed both: (1) double the array size, and (2) rehash the elements already present in the initial hash table — they may need new locations.
7.3.3 Worked Example — Collisions Degrade Performance (N = 4)
The first example builds the need for rehashing. We use a very simple hash function — the number of characters in the element, mod — purely for illustration. The bucket array has capacity , and the elements are animal names.
The hash function is
Insertion proceeds as follows:
| Element | Length | Bucket | |
|---|---|---|---|
| Elephant | 8 | 0 | |
| Otter | 5 | 1 | |
| Badger | 6 | 2 | |
| Cat | 3 | 3 |
Every bucket holds exactly one element — a perfect spread. Now check whether the table contains panda. Panda has 5 characters, so . We go to bucket 1, check the list, panda is not there, and the search is over — one bucket visit, constant time. This is the hash table doing what it is meant to do.
Now insert koala. Koala has 5 characters, so — a collision with Otter. Since we are using separate chaining, koala is appended to the end of bucket 1's list. Search for panda again: panda still hashes to bucket 1, but now the list has two elements (Otter, koala), so the search checks two entries instead of one.
Now insert alligator. Alligator has 9 characters, and — it joins bucket 1 as a third element. If every new element keeps landing in bucket 1, the performance of the hash table degrades to the performance of a linked list. If any bucket gets too full, the dictionary loses its advantage of one lookup for elements. Check the load factor at this point: after 7 elements in 4 buckets, , way above 1 — the table holds more entries than cells, so collisions are guaranteed. This is the situation that demands rehashing — the load factor exceeding the bound is the trigger.
7.3.4 Worked Example — A Full Rehash Walkthrough (N = 6 → 12)
The second example re-runs the same idea with the load factor enforced. The hash function is still the number of characters mod , now with capacity and the chosen load-factor bound 0.75.
| Element | Length | Bucket with | Load factor after insert |
|---|---|---|---|
| Elephant | 8 | 1/6 | |
| Cat | 3 | 2/6 | |
| Fish | 4 | 3/6 = 0.5 | |
| Woodchuck | 9 | (collides with Cat, chained) | 4/6 ≈ 0.6 |
| Dog | 3 | (collides again, would chain) | would be 5/6 ≈ 0.8 |
Track the load factor as we go. After Elephant and Cat it is , far below 0.75. After Fish it is — still manageable. After Woodchuck it is , about 0.6 — still fine. But Dog is the fifth element: inserting it would push the load factor to , about 0.8, which is above the 0.75 bound we chose. So we cannot insert Dog into the current table — the load factor would exceed the limit. The solution is to rehash first.
Step 1 — Double the capacity. The new bucket array has capacity (indices 0 through 11).
Step 2 — Re-insert every existing element using the same hash function — number of characters mod :
| Element | Length | Bucket with | Change? |
|---|---|---|---|
| Elephant | 8 | moved from 2 to 8 | |
| Cat | 3 | stays in 3 | |
| Fish | 4 | stays in 4 | |
| Woodchuck | 9 | moved from 3 to 9 | |
| Dog | 3 | stays in 3 |
Step 3 — Insert the new element. With the rehash complete, the load factor is , about 0.33. Now Dog can be inserted: , and after insertion the load factor is , safely below 0.75.
Elephant moved from bucket 2 to bucket 8 and Woodchuck moved from bucket 3 to bucket 9 — the new modulus changed their positions. Cat, Fish, and Dog happened to keep their buckets, and note that Cat and Dog both still land in bucket 3 even after the rehash; the instructor flagged that such outcomes happen and are fine. The takeaway is the one stated in Section 7.3.2: when you rehash, the elements already present in the initial hash table may need to find new locations. Rehashing is not just doubling the array — it is re-inserting every existing element under the new capacity.
The instructor also noted that the element limit on the array is in the sense that valid indices run from 0 to , which is distinct cells. A common slip is to write "the last index is "; the last index is , because the count starts at 0.
7.3.5 Assumptions and Scope
Assumption: Rehashing works on top of a good hash function and a fixed bound on the load factor. The trigger is the bound: crossing it starts the grow-and-rehash cycle. If the hash function is bad, rehashing only relocates the same collision patterns into the new array — the number of collisions does not drop. Scope: Rehashing does not make the hash function better, and it does not reduce the time complexity of operations (the load factor simply returns to a small constant). It is also expensive — every existing element is moved once. The choice of when to pay that cost is the load-factor bound: lower bounds mean more frequent, cheaper rehashes; higher bounds mean rarer, costlier ones and longer average chains in between.
7.3.6 Visual Intuition — The Bucket Array Before and After
Picture the bucket array as a row of boxes with a label over each one, and the hash function as the rule "look at the label on the box". With , the labels are 0 through 5, and Elephant (8 characters) reads label 2. After doubling, the row has twelve boxes labeled 0 through 11; Elephant now reads label 8 — the old label 2 is still there, but the rule points elsewhere. The visual landmark is the moment of the switch: at the same instant the row doubles in length, every element walks to its new box under the new labels. The one-sentence takeaway: the array doubles and the elements redistribute in a single synchronized step; elements whose new bucket differs are the visual proof that the old positions are meaningless under the new size.
7.3.7 Common Pitfalls
- Doubling the array but forgetting to rehash. The elements keep their old indices, the compression map changes underneath them, and lookups for already-stored keys go to the wrong cells. Growing without re-inserting is not rehashing.
- Thinking the hash function changes. The rule (e.g., "count the characters") stays the same; only the modulus — the compression map — changes because changed. The exam phrasing "modify the hash function?" is answered "no; the compression map is what changes".
- Believing rehashing lowers the time complexity. It restores the load factor to a small constant; it does not change as a function of the load factor, and identical keys still collide in the same buckets after the move.
- Forgetting that the load factor rises again. Growth is a repeating cycle: after the rehash the table is about half full, and as new entries arrive the load factor climbs back to the bound, triggering the next doubling.
7.3.8 Student Questions and Answers
Q: When we rehash, do we create a new hash table of double the size? A: Yes — we create a new hash table (a new bucket array) of double the size, not a bigger bucket. Then every element already present must be rehashed into it under the new compression mapping.
Q: Rehashing will be slow. Can we avoid it? A: Rehashing is costly, and creating a good hash function is an overhead in itself. You have to handle one or the other: either manage the complexity of creating a good hash function, or bear the operational difficulties of collision handling. It all depends on the application. If you are sure about the number of incoming elements, you can go with a simple hash function and a large capacity allotted initially — if you can manage the storage. If the table is too big for that, you may not opt for such a hash function; you pick the hash function that reduces the operational complexities.
Q: Does rehashing decrease the time complexity? A: No — rehashing will not decrease the time complexity. In the previous example, if you had animals with only three letters, rehashing would not reduce the complexity, because they still collide in the same buckets. Sometimes the honest decision is not to use a hash table at all. And note: if the load factor rises again after rehashing, we must rehash again — the growth keeps repeating.
Q: In reality it is not like 10 becomes 26 becomes 12, right? A: Correct. In practice you should have a pretty good idea of how much storage you will need, so the growth is planned rather than a sequence of doubling surprises. The exam problems are different — there you work the mechanics with the given numbers.
Q: Can you give an example using the Fibonacci sequence for hashing? A: Not today — there is still the binary search tree material to cover and a question paper to discuss. The Fibonacci-based idea is there in the material for you to look at on your own.
Recap + Bridge: Growing the bucket array forces two linked actions — change the compression map to the new size, then re-insert every existing element under the new map. That pair of actions is rehashing, and it is the enforcement mechanism behind the claim of Section 7.2. Next we turn from preventing collisions to the second line of defense: open addressing, where the table itself absorbs the colliding items.
Real-world connection: the instructor illustrated rehashing with how cloud storage accounts behave — the same pattern used by providers such as Dropbox. When you buy a large storage allotment, the provider does not hand you the complete capacity at once. They allot a portion — roughly half of what you asked — and when your usage reaches about 75 percent of what has been allotted, a notification fires ("this user has used this much") and they double the allotted capacity. The allotment grows incrementally, exactly the way the hash table doubles its capacity at the load factor. Compared with the cost of allotting complete storage initially, the incremental cost of rehashing is acceptable.
7.4 Open Addressing — an Overview
7.4.1 The Idea — the Table Is Open to Colliding Items
Separate chaining is one method of handling collisions, but it may not always be the right one, for reasons that become clear once we see the alternative. The next method is open addressing. The name can mislead: open addressing does not mean the table is "open" in any loose sense. It means the hash table is open to the colliding item as well — the colliding item is placed in a different cell of the same table.
Hook: Separate chaining solved collisions by attaching lists to buckets. But what if you cannot afford lists at all — because memory is tight? Open addressing is the answer that uses no extra structures, and it changes how insert, find, and delete all work.
Compare the two. In separate chaining, whenever there is a collision we attach a list to the bucket cell and store the colliding item in that list — a separate structure outside the bucket array. In open addressing, the item and the colliding item are placed in the same bucket array, at different locations. There is no secondary structure; the colliding key just lives in another cell of the same array.
7.4.2 The Space Trade-off
The immediate payoff of open addressing is space: no extra ADTs, no linked lists, no auxiliary structures. We save space — useful when we are short of space. The cost is on the other side of the ledger: the complexity of dealing with collisions is higher, because finding that other cell takes work, and so does searching for it later. So the trade-off is clean — if you don't manage more space, open addressing is the approach; if you can afford the extra structure, separate chaining keeps collision handling simpler.
Scope: Open addressing is the right tool when memory is the scarce resource and the expected number of elements is known roughly — because every cell holds at most one item, the load factor can never exceed 1, and the table must be sized for the largest expected population. It is the wrong tool when deletions are frequent (see Section 7.6) or when the worst case matters and you need the predictable behavior of chaining.
7.4.3 The Three Methods
Under open addressing there are three methods, which the session covered one by one:
- Linear probing — place the colliding item in the next available cell.
- Quadratic probing — probe cells at squared offsets from the original hash.
- Double hashing — use a second hash function to compute the probe step.
All three are "simple methods", as the instructor put it, and each has its own failure mode — the next sections develop each one with full examples.
7.4.4 Comparison — Separate Chaining vs Open Addressing
| Dimension | Separate chaining | Open addressing |
|---|---|---|
| Where colliding items live | In a linked list attached to the bucket | In another cell of the same array |
| Extra structures | One linked list per bucket (memory overhead) | None — this is the space saving |
| Load factor allowed | Can exceed 1 (lists share buckets) | Must stay below 1 (one item per cell) |
| Collision handling cost | Append to a list; find walks the list | Probe cells; find replays the probe sequence |
| Deletion | Simple — remove from a list | Tricky — needs an available marker (Section 7.6) |
| Typical failure | One bucket's list grows long | Clustering or probe sequences that miss empty cells |
The one-sentence rule for choosing: if you can afford the extra structure, separate chaining keeps collision handling simple; if you are short of space, open addressing saves memory and you pay with probe arithmetic.
7.4.5 Visual Intuition — One Array, Two Ways of Filling
Picture a row of cells. Separate chaining draws a chain hanging below a cell whenever two keys land there — the picture grows downwards, out of the array. Open addressing instead fills sideways: a colliding key moves to a neighbouring cell (linear), jumps to a squared-off distance (quadratic), or hops by a key-dependent step (double hashing). The visual landmark is the same in all three: no structure grows outside the row, and the row itself gradually fills. The takeaway: open addressing turns the "overflow area" of chaining into probe arithmetic inside the same array.
Recap + Bridge: Open addressing absorbs collisions inside the bucket array itself, saving the memory of chaining and paying with probe work. We now study the first and simplest probe rule — linear probing — where the colliding item takes the next free cell.
Real-world connection: open addressing is the layout behind many in-memory hash tables and caches, including parts of CPython's dictionary implementation and several database buffer pools — places where memory is scarce, locality matters, and chaining's pointer chasing would cost extra cache misses.
7.5 Linear Probing
7.5.1 The Probing Rule
Linear probing handles collisions by placing the colliding item in the next available table cell — more precisely, the next circularly available table cell, with all positions taken mod . Don't forget the mod: the search wraps around the end of the array.
The insertion rule in full: hash the key with . Whatever index you get, check that cell first. If the location is available, place the item there. If it is not, check the next cell, then the next, moving through the array until an empty bucket accepts the new entry.
Formalize. We try to insert an entry into bucket , where . The probe sequence is:
- — the bucket array (the table).
- — the key of the entry being inserted.
- — the value associated with the key.
- — the hash function giving the starting cell.
- — the starting index.
- — the probe number: is the cell the key hashed to, the next cell, and so on.
- — the capacity; the makes the probe wrap around, so the sequence is circular.
We check , then , then , and so on, continuing until we find an empty bucket that can accept the entry. If there is no empty bucket at all, the item cannot be stored — the table is full and we must rehash.
Each table cell inspected is referred to as a probe (one inspection of one cell), and the process of inspecting cells one after another is called probing. The first cell probed is the one the key actually hashed to; the ones after it are the displaced positions.
7.5.2 Worked Example — Insert 18, 41, 22, 44, 59, 32
The hash function is , and the table starts empty. Watch where each key lands.
- (13 goes into 18 once, leaving remainder 5) → cell 5, free. 18 goes to 5.
- (13 goes into 41 three times, 39, remainder 2) → cell 2, free. 41 goes to 2.
- (13 goes into 22 once, remainder 9) → cell 9, free. 22 goes to 9.
- — 13 goes into 44 three times, 39, remainder 5. Cell 5 already holds 18, so we have a collision, resolved with linear probing (not separate chaining):
Cell 6 is free → 44 goes to 6.
- (52 is 13 × 4, remainder 7) → cell 7, free. 59 goes to 7.
- (26 is 13 × 2, remainder 6). Cell 6 already holds 44 — collision. Probe:
Cell 7 holds 59 — still occupied. Probe again: Cell 8 is free → 32 goes to 8.
The final table (only the occupied cells shown):
| Cell | 2 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|
| Key | 41 | 18 | 44 | 59 | 32 | 22 |
Sense-check. Six keys were inserted; six cells are occupied (2, 5, 6, 7, 8, 9), and each key sits either in its own hash cell or in the first free cell after it — which is exactly what linear probing promises.
7.5.3 Searching in a Linear-Probed Table
The extra complexity of linear probing shows up at search time. To search for a key , apply the same hash function and probe the same circular sequence — you cannot stop at the first occupied cell.
Search for 32. , so start at cell 6. Cell 6 holds 44 — not 32. Does that let us conclude 32 is absent? No — 32 might have been displaced by probing. We already know collisions were resolved with linear probing, so we keep probing circularly: cell 7 holds 59, cell 8 holds 32 — found. The extra inspection is exactly why linear probing has extra complexity: you cannot claim retrieval in every case.
The reason we must keep probing: a displaced key never sits where its hash value points — it sits further along the probe sequence, and the only way to reach it is to walk the same sequence the insertion walked.
Search for 21. . Cell 8 holds 32, not 21. Probe cell 9 — 22, not 21. Probe cell 10 — empty. Stop and conclude that 21 is not present. Why is stopping at an empty cell correct? Because if 21 had been inserted, it would have been placed in the first free cell at or after its hash position — cell 10 would not be empty. An empty cell is a guarantee that no further probing can find the key.
The rule: probe until you find the key or until you find an empty cell. This stopping rule is the search analog of the load-factor discipline from Section 7.1 — the load factor has to be considered, because you can fill the array only up to the load factor (0.75 or whatever you kept); you never let the table get so full that searches walk long distances.
7.5.4 The Find and Insert Algorithms
Find element with key K: start at cell . Probe consecutive cells until one of the following occurs:
- An item with key is found — return it.
- An empty cell is found — the key is not in the table (because it would have been placed at or before this cell).
- All cells have been successfully probed — the key is not present.
The second rule is the subtle one, and it is what makes deletion tricky (Section 7.6). An empty cell ends the search only if it was never occupied — we will need a way to tell "never used" apart from "used and later removed".
Insert item (K, V): hash the key, then probe consecutive cells until one of the following occurs:
- An empty cell or an available cell is found — insert there.
- All cells have been unsuccessfully probed — there is no empty location; the table is full and an error or rehash is needed.
7.5.5 Assumptions and Scope
Assumption: The find/insert rules assume the load factor is kept below 1 (in this session, at 0.75 or below) so that an empty cell almost always exists somewhere along the probe path. If the table fills completely, rule 3 of both algorithms takes over and the operation fails — that is why the table is resized before it is ever allowed to fill. Scope: Linear probing assumes the same hash function and the same step size 1 for every key. If insertions used one rule and searches another, searches would walk the wrong cells. And the constant-time claim applies only to the expected cost under a good hash function; a long cluster makes a single search walk many cells (Sections 7.7 and 7.11).
7.5.6 Visual Intuition — The Wrap-Around Scan
Picture the 13 cells of the example as a clock face numbered 0 to 12, with the hand starting at the hash value. Insertion moves the hand clockwise, cell by cell, until it lands on a free cell; the hand may cross the top of the clock (cell 12 back to cell 0) — that crossing is the wrap. Search starts the hand at the same place and moves it clockwise, cell by cell, stopping when it finds the key or hits a gap. The landmark to notice: a key that collided at cell 6 may finally sit at cell 8 — so on the clock face, "where is 32?" is answered by sweeping the arc 6 → 7 → 8, not by looking at 6 alone. The one-sentence takeaway: linear probing is a clockwise sweep from the hash value, and a gap ends the sweep.
7.5.7 Common Pitfalls
- Stopping at the first occupied cell during a search. The occupied cell only proves something is there; the key you want may have been pushed further along by probing. You must continue the sweep.
- Forgetting the circular wrap. is not optional: a probe sequence that stops at the end of the array would miss cells at the start.
- Claiming every search is . Search cost is expected-constant under a bounded load factor; probing adds real work, and a long cluster turns a search into a near-linear scan.
- Treating an empty cell as "end" when it was once occupied and then deleted. A deleted-but-unmarked cell looks empty to a search and cuts the sweep short — this is precisely why Section 7.6 introduces the available marker.
7.5.8 Student Questions and Answers
Q: After all the insertions, how do you search for 32? A: Apply the same hash function: 32 mod 13 = 6. Cell 6 is occupied by 44, so you do not conclude that 32 is absent — you know linear probing was used, so you keep searching circularly: 7 holds 59, 8 holds 32. That extra probing is the complexity linear probing carries; retrieval is not always constant time.
Q: When searching, if we reach an empty cell, can we stop and conclude the key is not present? A: Yes. If 21 was present, it would have been inserted in this location — cell 10 would not have been empty, because linear probing places every key in the first free cell at or after its hash position. So you probe until you find the key or until you find an empty cell.
Q: Is the time complexity of search always O(1) with a hash table? A: No — the time complexity is not always O(1). The constant-time claim is the expected behavior under a good hash function and a bounded load factor; probing adds work, and the worst case is much worse, as Section 7.11 quantifies.
Recap + Bridge: Linear probing resolves a collision by placing the item in the next circularly available cell; searching replays that sweep and stops only at a match or a genuinely empty cell. The sweep works only while the table is never allowed to fill — and deleting items while keeping the sweep correct turns out to be the hard part, which we handle next with the available marker.
Real-world connection: linear probing is the collision strategy inside several real hash tables because it has the best cache behavior — consecutive cells are nearby in memory, so probing walks a contiguous cache line instead of chasing pointers. CPython's dictionaries and Ruby's hash tables use open addressing variants with this locality advantage; the price they pay is exactly the discipline of this section: keep the load factor bounded, or the sweep becomes a scan.
7.6 Deletion in Open Addressing
7.6.1 The Available Marker
To handle insertions and deletions in an open-addressed table, we introduce a special object called available, which replaces deleted elements. The removal procedure: first search for an item with key — applying the same hash function used at insertion and probing the same sequence. If the item is found, replace it with the special item available and return the element . The cell is marked, not emptied.
Hook: Deleting from a linear-probed table looks easy — just remove the item. But a plain removal silently breaks every search that passes through that cell. The fix is a marker that costs almost nothing: why does it work, and what breaks without it?
Why the marker? If we simply delete the element and keep the location empty, the find operation would mistake that location for an initially empty cell. Consider a search that is probing for some other element and reaches the deleted cell: it would think this location was empty from the beginning, stop the search there, and report that the element is not found — even though the real answer sits further along the probe sequence. So we must be very clear when a location was deleted: the available marker distinguishes a deleted location from an initially empty one. A search continues past available cells (they are not "empty" for search purposes) but stops at truly empty cells; an insert can use either an empty cell or an available cell.
Formalize. Every cell in the table is in exactly one of three states:
- Empty — never used. A search stops here: no key beyond this point can be in the table.
- Occupied — holds a live entry . A search compares keys here.
- Available — once occupied, then deleted and marked. A search passes over it; an insert may reuse it.
So the probe rule changes by operation:
The distinction is what keeps the stopping rule of Section 7.5.4 honest: only a cell that was never used guarantees that no displaced key sits beyond it.
7.6.2 Worked Example — Deletion and the Available Marker
Reuse the linear-probed table of Section 7.5.2: cell 5 holds 18, cell 6 holds 44, cell 7 holds 59, cell 8 holds 32, cell 9 holds 22.
Step 1 — Delete key 44. Search for 44: , cell 5 holds 18; probe to cell 6, which holds 44 — found. Replace it with the special marker: cell 6 becomes available.
Step 2 — Search for 32. . Cell 6 is available — not empty — so the search continues: cell 7 holds 59, cell 8 holds 32 — found. The marker let the sweep pass through the deleted cell.
Step 3 — What goes wrong without the marker. If we had instead set cell 6 to empty, the same search for 32 would stop at cell 6 and report "32 not present" — even though 32 sits two cells further along. The deletion of an unrelated key would have destroyed the reachability of a key that never collided with it directly.
Step 4 — Insert a new key with hash 6. Suppose a new key hashes to 6. The insert rule accepts an available cell, so can be placed at cell 6. That is safe: the deleted key 44 is gone forever, and any future search for 44 will walk past cell 6, never find it, and correctly stop at the next empty cell.
Sense-check. After the steps, every search still follows the same sweep rules as before the deletion: the marker only changed what the sweep sees at cell 6 — pass-through instead of stop.
7.6.3 Assumptions and Scope
Assumption: The marker scheme assumes the table never reaches the state where the probe sequence is full of occupied and available cells — the load factor still has to be kept bounded, exactly as in the other open-addressing methods. Rehashing and the load-factor check apply here too; the instructor did not repeat them for every method. Scope: Available cells cost the table space: they count toward the load factor but hold no live data, and a table full of markers can degrade searches to a full sweep even with few live entries. The worst case for all hashing is ; if you are sure you will face the worst case, don't go for a hash table at all.
7.6.4 Visual Intuition — Three Kinds of Cells
Picture the bucket row with cells painted in three colors: white for never-used, gray for occupied, and striped for deleted-but-available. A search walks right from the hash position and stops at the first white cell; striped cells are just part of the road. The visual landmark is a striped cell sitting between two occupied cells — it proves a key was removed, and it is precisely the cell that would have broken the sweep if it were white. The takeaway: the marker turns "this cell has no key" (empty) into "this cell has no key and never did" (empty) versus "a key was here once" (available) — three meanings, one bit of bookkeeping.
7.6.5 Common Pitfalls
- Deleting by clearing the cell. Setting a deleted cell to empty breaks every probe sequence that passes through it — searches stop early and report "not found" for keys that are still in the table.
- Searching that treats available as empty. The whole point of the marker is that searches pass over it. A search that stops at an available cell repeats the same failure as a cleared cell.
- Forgetting the memory cost. Deletion does not return the cell to the table's usable capacity; available cells still occupy space and count toward fullness, so heavy insert-delete cycles can leave a sparse, marker-filled table that must be rehashed to recover.
- Ignoring the load factor after deletions. Even with markers working correctly, the load factor discipline from Section 7.1 still applies — rehash triggers and the load-factor check are not suspended for open addressing.
7.6.6 Student Questions and Answers
Q: Why not just remove the element and keep that location empty? A: Because find would think that location was empty from the beginning. When it is searching for some other element and comes to that empty location, it stops the search and returns "not found" — even though the element exists further along the probe sequence. The available marker differentiates a deleted location from an initially empty one.
Q: All these operations seem to be memory intensive. Is that a problem? A: Yes, the operations do cost memory — you cannot help it. You have to choose the hash table very wisely: either you pay for the structure of separate chaining, or you pay for the probing and marking overhead of open addressing.
Exam note: this behavior applies across all open addressing methods — rehashing and the load-factor check apply here too; the instructor did not repeat them for every method. The worst case for all hashing is ; if you are sure you will face the worst case, don't go for a hash table at all.
Recap + Bridge: Deletion in open addressing replaces the removed element with the available marker so that searches pass through the cell and still stop correctly at genuinely empty cells. With insert, find, and delete working, we can now study what linear probing does badly — the pile-up of keys known as primary clustering.
Real-world connection: the available (deleted) marker is standard practice in production open-addressed tables — for example, in systems where keys expire or records are removed (caches, session stores). When such tables fill with markers, real implementations trigger a rehash that also purges markers, which is why the delete-heavy workloads in databases and caches are usually better served by chaining or by a different structure entirely.
7.7 Primary Clustering
7.7.1 What Primary Clustering Is
Linear probing has a characteristic failure mode called primary clustering. The instructor showed it with a sequence of insertions, then asked the class to name the pattern.
Hook: Linear probing always moves one cell at a time — so what happens when many keys keep landing on the same stretch of cells? The table stops looking like a hash table and starts looking like a queue: a growing block of occupied cells with everyone probing deeper into it.
Consider inserting the keys 18, 41, 22, 44, 59, 32, 31, 73 into an initially empty table of size 13 with :
- 18 → 5; 41 → 2; 22 → 9.
- 44 → 5 (occupied) → 6.
- 59 → 7.
- 32 → 6 (occupied) → 7 (occupied) → 8.
- 31 → 5 (occupied) → 6, 7, 8, 9 all occupied → 10.
- 73 → 8 (occupied) → 9 (occupied) → 10 (occupied) → 11.
Cells 5 through 11 are now one solid block. Every one of these keys originally hashed to 5, 7, 8, or 9, and linear probing stacked them into a contiguous run.
Primary clustering is the tendency of elements to cluster around the table locations they originally hashed to. Elements like 44, 31, and 32 all wanted to sit near their initial hash values (5, 5, 6), and the collision-resolution rule placed each one immediately after the last collider, so the consecutive locations get filled. There may be empty locations elsewhere in the table — but the colliding items all pile up next to the values they originally hashed to, producing a cluster.
7.7.2 Worked Example — All Keys Hash to the Same Value (N = 17)
The second example is sharper. Insert the elements 0, 17, 34, 51, 68, 85, 102 into a table with 17 locations (cells 0 through 16). The special property of these elements: every one of them hashes to the same initial value, because each is a multiple of seventeen, and
The insertions:
- 0 → 0 (free).
- 17 → 0 (occupied) → 1.
- 34 → 0 (occupied) → 1 (occupied) → 2.
- 51 → 0 → 1 → 2 → 3.
- 68 → 0 → 1 → 2 → 3 → 4.
- 85 → 0 → 1 → 2 → 3 → 4 → 5.
- 102 → 0 → 1 → 2 → 3 → 4 → 5 → 6.
Seven elements occupy cells 0 through 6 consecutively. Notice what the table looks like: cells 7 through 16 — ten cells — are still empty. Even so, every insertion probed a growing prefix of the array. All the values that hash to the same initial value end up stored in consecutive locations, and searching any of them walks the whole cluster.
Sense-check. The pattern is a straight arithmetic sequence: the k-th element inserted probes and lands at cell . With seven elements, the cluster runs from cell 0 to cell 6, exactly as observed.
7.7.3 Worked Example — The Outsider Suffers
Now try to insert the element 20. The hash value of 20 is — 20 does not even belong to the group of elements hashing to the same initial value. But cell 3 is occupied (it holds 51), so 20 must probe onward:
- Cell 3 holds 51 — occupied.
- Cell 4 holds 68 — occupied.
- Cell 5 holds 85 — occupied.
- Cell 6 holds 102 — occupied.
- Cell 7 — free. 20 lands at cell 7.
An element whose own hash position is 3 had to walk five probes to find a free cell, even though ten cells (8 through 16) are empty. 20 suffers because of primary clustering even though it hashes elsewhere. That is the real harm of primary clustering: the penalty bleeds over to keys that had nothing to do with the original pile-up. An element that should have landed near its own hash value must instead walk across the whole cluster.
7.7.4 Assumptions and Scope
Assumption: Primary clustering is a property of linear probing under any load factor — the step size is 1, so every collision extends an existing run by one. The damage scales with how full the table is: the cluster-forming probability grows as the load factor rises, because an empty cell at the end of a run is harder to find. Scope: Primary clustering is a performance failure, not a correctness failure — searches still end correctly, they just walk longer stretches. It is also specific to linear probing: quadratic probing (Section 7.8) removes the bleed-over to outsiders, and double hashing (Section 7.10) removes the shared-sequence problem entirely, each at the price of more arithmetic.
7.7.5 Visual Intuition — The Growing Block
Picture the 17-cell table as a row of boxes. The multiples of seventeen all drop into box 0, so the overflow spreads right: box 0 fills, box 1 fills, box 2 fills — a solid block creeping rightward while boxes 7 through 16 stay empty. The landmark is the frontier of the block: every new collider pushes the frontier one step right, and any outsider whose hash value lies inside the block (like 20 at cell 3) must traverse the entire block to get past it. The one-sentence takeaway: a cluster is a contagious block — colliders extend it, and innocents must pay to cross it.
7.7.6 Common Pitfalls
- Thinking primary clustering only hurts the colliding group. The outsider 20 pays the full cost of a cluster it never caused — the harm bleeds over to keys that hash elsewhere.
- Believing a good hash function makes clustering impossible. A good hash function makes collisions rare, but it cannot rule them out; as soon as a collision lands inside a cluster, linear probing extends it. Guaranteeing no collision ever happens is itself a big challenge, so the problem does not simply go away.
- Comparing linear probing to linear search only as a joke. The comparison is technically real: when a cluster forms, probing consecutive cells is close to a linear scan. The arithmetic (which cell next) is simple to compute; the pain is the number of cells you may have to inspect, which is why the load factor must be kept low.
- Ignoring the empty cells next door. The table can be half empty and still force long probes, because the cluster occupies a contiguous stretch — empty cells elsewhere do not help a key that must cross the block.
7.7.7 Student Questions and Answers
Q: What is primary clustering? A: Elements tend to cluster around the table locations that they originally hashed to. Because linear probing places every colliding item next to the value it originally hashed to, the consecutive locations get filled — even when empty locations exist in other places. That is primary clustering.
Q: Why does the element 20 suffer? Twenty is not even part of the group of elements hashing to the same initial value. A: Correct — 20 mod 17 is 3, and it does not belong to the group that all hashes to 0. But cell 3 is occupied by the cluster, so 20 has to probe 3, 4, 5, 6, 7 before finding a free cell. Elements that do not belong to the group still suffer because of the cluster, and you cannot find a quick location for them. That is the disadvantage of primary clustering.
Q: Can we come out of this by coming up with a good hash function? A: Only if you can be 100% sure there will never be a collision. If you do not know which elements are going to come in the future, and a collision is possible, you have to resolve to one of the collision handling methods — separate chaining, linear probing, quadratic probing, or double hashing — and each has plus points and negative points. Guaranteeing no collision ever happens is itself a big challenge, so the problem does not simply go away.
Q: Linear probing is more or less like a linear search, right? A: Yes — when a cluster forms, probing consecutive cells is close to a linear scan. The arithmetic (which cell next) is simple to compute; the pain is the number of cells you may have to inspect, which is why the load factor must be kept low.
Recap + Bridge: Primary clustering is the tendency of linear probing to stack colliding keys into a contiguous block — a block that grows with every collision and that outsiders must cross. The fix changes the step: instead of moving one cell at a time, quadratic probing jumps by squared offsets, which stops the bleed-over — and buys a new, milder problem called secondary clustering.
Real-world connection: primary clustering is the textbook reason real systems do not use plain linear probing on unknown workloads — the danger is precisely the "everybody piles onto the same corridor" failure that shows up in memory allocators and caches under skewed access patterns. Systems that must survive adversarial or skewed key distributions prefer quadratic probing or double hashing, or fall back to chaining, all to avoid the block-of-cells failure you saw here.
7.8 Quadratic Probing
7.8.1 The Probing Sequences
Quadratic probing is the second open-addressing strategy, and it is a small variation on linear probing. Instead of probing away from the hash value, we probe at squared offsets. Two equivalent forms were used in the session:
Formalize. The probe sequence is the hash value plus a quadratic term, taken mod :
or
- — the ordinary hash function giving the starting cell.
- — the probe counter, running up to whatever the insertion needs (in the first form often starts at 0, which gives the hash cell itself).
- — the capacity; the keeps every probe inside the array.
Both forms contain a quadratic term — that is what makes the method quadratic probing — and the instructor said outright: either is acceptable if the exam does not specify, but you must pick one and use it consistently for every element (Section 7.8.3). If the probe formula is meant to be a specific one, it will be mentioned in the question. The general textbook form is with positive constants ; the session's is the case, and is the case.
7.8.2 Worked Example — Insert 76, 40, 48, 5, 55
The table has capacity , the hash function is , and collisions are resolved with quadratic probing using .
- (7 × 10 = 70, remainder 6) → cell 6, free. 76 goes to 6.
- (35 is 7 × 5, remainder 5) → cell 5, free. 40 goes to 5.
- (42 is 7 × 6, remainder 6) — cell 6 holds 76. Collision, so probe with :
Cell 1 is free → 48 goes to 1.
- — cell 5 holds 40. Collision:
Cell 0 is free → 5 goes to 0.
- (49 is 7 × 7, remainder 6) — cell 6 holds 76. Collision:
55 goes to 4.
Final table:
| Cell | 0 | 1 | 4 | 5 | 6 |
|---|---|---|---|---|---|
| Key | 5 | 48 | 55 | 40 | 76 |
Sense-check. Every key sits either in its own hash cell or at the first probe position the squared-offset sequence produced; no key was placed by the linear step of 1. The arithmetic for the 5 and 55 insertions was checked live during the session against the formula stated to be fixed — used consistently for all five keys — and the probe sequences above are exactly what that formula produces (for 5: ; for 55: ).
7.8.3 The Consistency Rule
You may choose either or , but you must use the same one for all elements and all probes. Using for one element and for the next is wrong — as is combining linear and quadratic probing in a single solution. Think about retrieval in a real system: we insert using one probing rule and retrieve using another — then we would have to remember which elements were inserted with which method. That cannot be done, so we never unnecessarily increase the complexity; the probe function stays consistent.
7.8.4 Assumptions and Scope
Assumption: Quadratic probing assumes a fixed probe formula with constant coefficients, applied uniformly to every key. It also assumes the load factor stays comfortably below 1: the quadratic probe sequences cover only a subset of cells, so an empty slot may be unreachable even when the table has space (Section 7.9 develops exactly this failure). Scope: Quadratic probing fixes the bleed-over of primary clustering — an outsider no longer walks the cluster's probe path — but it does not fix the shared-sequence problem: two keys with the same initial hash still follow identical probe sequences. And if is composite, the probe set may revisit only a fraction of the cells; choosing prime mitigates this (Section 7.9.3).
7.8.5 Visual Intuition — Jumping Farther Each Time
Picture the 7-cell table as a row of boxes, with the hash position as a start point. Linear probing's sweep takes steps of 1, 1, 1; quadratic probing takes steps of 2, 4, 6, 8, … (that is, the offsets grow as , and the difference between successive offsets is ). The visual signature is a jump that gets longer each probe: 48 jumps from cell 6 to cell 1 in one bound; 55 jumps 6 → 1 → 5 → 4, hopping over the occupied stretch instead of inching through it. The takeaway: squared offsets scatter the probes widely, which is why clusters do not grow as contiguous blocks — and why the same spread can miss cells entirely.
7.8.6 Common Pitfalls
- Switching probe formulas mid-solution. for one key and for the next gives inconsistent placements; searches under one formula cannot find keys inserted under the other. This is the instructor's explicit warning: never combine methods, and never combine formulas.
- Mixing linear and quadratic probing in one table. A key inserted by linear probing is unreachable by a quadratic search and the other way around; the probing method is a property of the whole table, not of individual keys.
- Starting the count inconsistently. The formula with gives probes ; the same formula written with gives different cells. Fix the starting value along with the formula and keep it fixed.
- Forgetting the mod on the probe result. is not a valid cell of a 7-cell table; the turns it into cell 4. Dropping the mod sends the probe outside the array.
7.8.7 Student Questions and Answers
Q: Which probe formula should we use when the exam does not specify one? A: Either H(K) plus i square or H(K) plus i plus i square is fine — pick one and use it consistently for all the elements. If a specific hash function is intended, the question will mention it.
Q: Can we combine linear and quadratic probing in one solution? A: No. You cannot combine linear and quadratic in one. The probe function has to be consistent for all elements — think of real cases: inserting with one probing rule and retrieving with another means we cannot know which elements were inserted with which method. We would only increase the complexity.
Q: Is the insertion of 5 correct in the worked example? A: Using H(K) + i + i² with N = 7: 5 hashes to 5, which holds 40; the first probe is (5+1+1) mod 7 = 0, which is free, so 5 lands at cell 0. (The slide walkthrough was checked live during the session and the presenter flagged a possible mistake there; the consistent formula is the one fixed above.)
Recap + Bridge: Quadratic probing replaces the one-cell step of linear probing with squared offsets — two standard formulas, one rule: pick a form and stay with it for the entire table. The squared jumps stop the contagious block of primary clustering, but keys sharing an initial hash still share a probe sequence — the milder problem called secondary clustering, which the prime-size choice helps to control.
Real-world connection: quadratic probing appears in real hash tables where cache-friendly sequential probes are not essential but adversarial skew must be damped — for example, in several database hash indexes and in the hash tables of some runtimes. The consistency rule is an engineering invariant, not just an exam rule: production tables store the probe parameters inside the table header, so that a table rebuilt with different parameters is always searched with the same parameters that built it.
7.9 Secondary Clustering
7.9.1 What Secondary Clustering Is
Quadratic probing cures one problem and inherits another. It fixes the bleed-over effect of primary clustering: a key that hashes to a value outside the cluster no longer suffers the cluster's probe sequence. But it introduces secondary clustering: all elements that hash to the same initial value follow the same probe sequence — the same offsets (or ) from the same starting cell. So the collision resolution for every member of a group is identical, and the group still collides with itself, just in a different pattern.
Formalize. Two keys and with produce probe sequences
which are the same sequence, because the starting cells are the same and the offsets depend only on . The starting cell determines the whole probe sequence: every key in the group probes the identical set of cells in the identical order, so the group keeps colliding with itself — the textbook calls this the milder form of clustering, because it does not drag in outsiders the way primary clustering does.
The deeper problem: quadratic probing may fail to find an empty slot even when the array is not full. The probe sequence revisits only certain cells (the squared offsets modulo ), so the insertion loop can run through its probe set and find every candidate occupied — while plenty of cells elsewhere in the array sit empty. This is especially true when is not chosen as a prime: then we may be unable to find an empty slot even when the array is at least half full. We may have half the locations empty and still cannot place the element, because the probe sequence keeps returning to occupied cells.
7.9.2 Worked Example — The Same Keys, Quadratically
Reuse the set 0, 17, 34, 51, 68, 85, 102 with capacity , hash , and quadratic probing with .
- 0: → cell 0.
- 17: , occupied. First probe: → cell 2.
- 34: , occupied. : , occupied. : → cell 6.
- 51: , occupied. Probes : cells 2, 6, and → cell 12.
- 68: , occupied. Probes: 2, 6, 12, then → cell 3.
- 85: . Probes: 2, 6, 12, 3, then → cell 13.
- 102: . Probes: 2, 6, 12, 3, 13, then → cell 8.
Every one of these elements hashes to 0, and every one walks the same probe sequence 0, 2, 6, 12, 3, 13, 8, ... — a fixed set of cells dictated by the squared offsets. That is secondary clustering: same initial hash, same probe sequence, so the group's members keep colliding with each other, and eventually a member of the group may find every cell of its probe sequence occupied even though the array has empty cells elsewhere.
Sense-check. Contrast with primary clustering: there, an outsider (20 in Section 7.7.2) got dragged into the cluster's cost. Here, an outsider does not suffer much — an element hashing to 3, say, probes 3, 5, 9, 15, 6, ... (its own squared offsets from its own start), which stays clear of the multiples-of-17 group's sequence, so it finds a location easily. The suffering in secondary clustering is confined to members of the same initial-hash group.
7.9.3 Why a Prime N Helps
Choosing prime mitigates the failure. With a composite modulus, the sequence (or ) revisits only a fraction of the cells; with a prime , the mod-N wrap-around reaches far more distinct locations before repeating. As the instructor put it: if you choose as prime, at least you may be able to find more locations compared to a non-prime , because the mod makes the probe cycle visit more distinct cells. The earlier session's example of why should be prime applies here directly.
Intuition + analogy: Think of the probe sequence as a traveller hopping along a circular track with markers, taking jumps of length (the offsets). If the track has a composite number of markers, the hop pattern and the track length share a common factor, and the traveller lands on the same small set of markers over and over — like a clock whose minute hand jumps by 12 minutes and only ever touches one third of the dial. A prime track length removes the shared factor, so the jumps spread over many more markers before a repeat. The analogy captures exactly why prime means "the probe cycle visits more distinct cells".
7.9.4 Assumptions and Scope
Assumption: The claim "prime visits more distinct cells" assumes the probe offsets and the modulus share no common factor — which a prime modulus guarantees automatically for any offset not divisible by it. Even with a prime , quadratic probing is not guaranteed to cover every cell; for the pure form the probe set covers about half the cells, so the table should stay below half full for full coverage. Scope: Secondary clustering is milder than primary clustering in one precise sense — the damage stays inside the initial-hash group and does not bleed over to outsiders. But it is still real: a hot group of keys can monopolize its probe set and make insertions for that group fail while other regions of the table sit empty.
7.9.5 Visual Intuition — One Track, One Group, Repeated Hops
Picture the 17-cell table as a circular track with markers 0 to 16. The multiples-of-17 group all start at marker 0 and hop to 2, then 6, then 12, then 3, then 13, then 8 — leaving marks only on this one track while markers 1, 7, 9, 10, 11, 14, 15, 16 stay untouched. A key that starts at marker 3 (an outsider) hops 3, 5, 9, 15, 6, ... — a different set of markers. The landmark is the untouched markers: a visitor inspecting the table sees large empty regions and still cannot place the next group member, because the group's track is exhausted. The takeaway: secondary clustering is a group-local exhaustion of one probe track, not a contagious block.
7.9.6 Common Pitfalls
- Assuming quadratic probing guarantees finding an empty cell. It does not: the probe sequence covers a limited subset of cells, and if those are all occupied, the insertion fails even with plenty of empty cells elsewhere. With a composite the subset shrinks further.
- Using a composite capacity "because it is close to prime". The whole benefit comes from the modulus being prime; a composite makes the probe cycle repeat after fewer distinct cells, which is precisely the failure mode of this section.
- Thinking secondary clustering also hurts outsiders. It does not — the defining contrast with primary clustering is that outsiders keep their own probe sequence and are not dragged into the group's cost. Confusing the two costs is a common exam slip.
- Forgetting that every member of the group pays the same sequence. Because the starting hash determines the whole probe sequence, adding one more group member re-walks the same cells — the group's collisions grow together, not independently.
7.9.7 Student Questions and Answers
Q: Please explain the second probe of 34 again. A: With H(K) + i + i², i runs 1, 2, 3, ...: the first probe is H(K) + 1 + 1, the second is H(K) + 2 + 4, the third is H(K) + 3 + 9, then H(K) + 4 + 16, and so on. For 34, the initial hash value is 0, so the probes are cell 2, then cell 6, then 12, then 3, then 13, then 8 (all mod 17).
Q: In primary clustering, the outsider 20 suffered. Is that also true in secondary clustering? A: No — that is the difference. In secondary clustering, a number outside the group does not suffer much: it follows its own probe sequence from its own initial hash value, and it does not have to go through the entire probe sequence of the group's members. The group's members, though, all share one probe sequence, which is the clustering.
Q: Which formula do we follow for quadratic probing — H(K) + i + i² or H(K) + i²? A: Follow one. In this session the fixed form was H(K) + i + i² — all of you follow the same thing, and use it consistently for every element. Don't confuse the two forms mid-solution.
Recap + Bridge: Secondary clustering is the group-local version of the clustering problem: same initial hash, same probe sequence, group members colliding with each other — and, with a bad modulus, a probe set that cannot reach empty cells. The final open-addressing method removes even this: double hashing makes the probe step depend on the key itself, so no two keys share a sequence.
Real-world connection: the prime-capacity rule is a hard engineering constraint in real hash tables — production tables size their arrays to prime (or near-prime) lengths, and when a table grows it often grows to the next prime rather than to a power of two, for exactly the reason of this section: the probe cycle must spread over distinct cells. Hash-table libraries for languages such as Java and C++ document prime table sizes; the trade-off is that resize math becomes slightly costlier, which is the price of avoiding group-local starvation.
7.10 Double Hashing
7.10.1 Two Hash Functions
Double hashing is the third open-addressing method, and the instructor's verdict was "simple — just a little more arithmetic". We use two hash functions. The first is the ordinary one, , which gives the starting cell. Whenever there is a collision, we compute a second hash function , and the probe sequence becomes
Formalize. With collisions, the probe sequence is:
- — the first hash function, giving the starting cell.
- — the second hash function, giving the probe step: how many cells ahead each probe jumps.
- — the probe counter, ; only changes between probes, never the two hash functions.
- — the capacity; the step is taken mod , so the jumps wrap around the array.
The second hash function is typically chosen in the form
where is a prime number. This is the format to use if the question does not give the second hash function explicitly: pick a prime , and . (In the example, .) It is this second function that ensures the collision is handled successfully — the probe step differs from key to key, which breaks the shared-probe-sequence problem of quadratic probing. A condition worth noting: the step must be relatively prime to the table size so that the probe sequence visits every cell before repeating — choosing both and prime guarantees this.
Be very clear on one point: the second hash function is used only when there is a collision. If the first hash lands on a free cell, is never needed. We apply two hash functions only in the collision case — then, when even the first probe with collides, we do not invent a third hash function: we keep the same two functions and just advance (Section 7.10.2).
Intuition + analogy: Two keys that collide should not then march in lockstep. Think of the probe step as the stride of a walker: in linear probing everyone shuffles one cell at a time, in quadratic probing everyone jumps the same growing lengths — so keys that start together stay together. Double hashing gives each key its own stride: one key steps by 3 cells, another by 5, so even keys that share a starting cell immediately diverge. The relationship: first hash = where you start, second hash = how long your stride is; keys with the same start but different strides never follow the same path.
7.10.2 Worked Example — Insert 18, 41, 22, 44, 59, 32
The table capacity is , the first hash is , and the second is . Both hash functions would be given in an exam question; otherwise use the format with prime.
- → cell 5, free. 18 goes to 5. (The second hash is not used — no collision.)
- → cell 2, free. 41 goes to 2.
- → cell 9, free. 22 goes to 9.
- — cell 5 holds 18, so there is a collision. Now (and only now) compute the second hash:
The probe with : Cell 10 is free → 44 goes to 10. Had cell 10 also been occupied, we would advance : , then , and so on — only changes, never the formula, because double hashing already uses two hash functions and there is no third one to invoke.
- (52 is 13 × 4, remainder 7) → cell 7, free. 59 goes to 7. No collision, so the second hash is not used.
- (26 is 13 × 2, remainder 6) → cell 6, free. 32 goes to 6. Again no collision.
Sense-check. All six keys sit either in their own hash cell (18, 41, 22, 59, 32) or at the first probe of their own stride (44 at cell 10). The probe step for 44 was 5 — the value of — which is exactly what moved it from cell 5 to cell 10, one stride of 5 cells. (The lecture worked 18, 41, 22 and 44 in full and stated the rule that is always 1, 2, 3, ... for further probes; the 59 and 32 placements shown here are the natural continuation of the same example — their cells are free, so they need no second hash at all.)
7.10.3 Assumptions and Scope
Assumption: Double hashing needs two hash functions that stay fixed for the lifetime of the table — on collision only advances, never the functions. The second hash must never evaluate to 0 (a step of 0 would loop forever on the same cell), and its values must be relatively prime to so the probe sequence covers all cells; the form with and prime ensures both. Scope: Double hashing costs the most arithmetic of the three methods — two hash computations on every collision — but it is the closest practical approximation to the ideal "uniform hashing" assumption used in the expected-probe analysis of Section 7.11. It is the method to choose when collisions must stay rare and the arithmetic cost is affordable.
7.10.4 Visual Intuition — Different Strides, Different Tracks
Picture two runners starting at the same cell on a circular track of 13 markers. Under linear probing both shuffle one marker per probe and stay side by side forever; under double hashing one runner's stride is 5 (44's stride) and another's is, say, 3 — after the first step they are already on different markers, and their paths never coincide again. The visual landmark is the first probe: keys with the same starting cell take their first step in different directions by different distances. The takeaway: the second hash creates per-key tracks, which is why double hashing avoids both primary and secondary clustering.
7.10.5 Common Pitfalls
- Computing the second hash when there is no collision. is used only when the first hash lands on an occupied cell. On an exam, computing it eagerly wastes time and is wrong procedure.
- Inventing a third hash function when the first probe collides. The rule is fixed: two functions, . A second collision just advances ; there is no third function to invoke.
- Letting be 0. If were possible, the step would wrap to 0 and every probe would revisit the same cell. The form keeps the step in , which rules this out.
- Forgetting that both hash functions are given (or that the default format applies). In an exam the functions are stated; if the second one is not given, use with prime, and say which you chose.
7.10.6 Student Questions and Answers
Q: How did 44 end up at cell 10? Where did the value 10 come from? A: The initial hash value of 44 is 5, but cell 5 is occupied, so we compute the second hash: 7 minus (44 mod 7) = 7 minus 2 = 5. The probe is H(K) plus i times H'(K), with i going 1, 2, 3, ... — so 5 plus 1 times 5 = 10. That is how you got 10.
Q: Do we compute the second hash function for every insertion? A: No. The second hash function is used only when there is a collision. Its form is Q minus (K mod Q), where Q is a prime number — in the example, Q is 7. If the first hash lands on a free cell, we never need it.
Q: Is double hashing hard to solve in an exam? A: The concept is simple — the manual operations are a little more than the other methods because there is a second hash to compute on collisions. All these methods have some number of operations; double hashing just has a few more calculations. The formula will be given in the question, and if not, you take the Q minus (K mod Q) format with Q prime.
Recap + Bridge: Double hashing resolves a collision with a key-dependent stride , where with prime — the second hash fires only on collisions, and only advances afterward. With all three open-addressing methods in hand, we now step back and ask how good they really are: the worst case, and the expected number of probes as a function of the load factor.
Real-world connection: double hashing is the method behind several high-performance hash tables where insertions are frequent and lookups must stay short — for example, in parts of the Linux kernel's hash table infrastructure and in some in-memory databases. Its per-key stride makes it the recommended open-addressing method under adversarial key sets, because no single group can monopolize a probe track; the price is two hash computations per collision, which modern CPUs absorb easily compared with the cost of a cache miss on a long cluster.
7.11 Complexity of Hashing — Worst Case and Expected Probes
7.11.1 The Worst Case
Many students, as the instructor noted, had not noticed until this session that the constant-time claim is conditional. The worst case for search, insertions, and removals on a hash table is
Hook: If the hash table is really O(1), how can there be a "worst case" at all? The answer is the same one that makes every conditional claim interesting: the O(1) is an average, and averages hide a much darker corner.
When does the worst case occur? When all the keys inserted collide — every key hashes to the same cell, every operation walks the whole pile. If the data guarantees this behavior, the hash table delivers nothing but linear work, and the honest engineering decision is to not use a hash table for that data at all. The load factor is the control knob: it affects the performance of the hash table directly, because it bounds how long the chains (or probe runs) can grow before the table resizes.
The O(n) corner: this is a worst case, not a common case — a good hash function makes it vanishingly unlikely with ordinary data. But "unlikely" is not "impossible": a fixed hash function can always be defeated by a key set chosen to collide (for example, multiples of a common factor against a mod-N function). This is why applications with hostile or adversarial inputs treat the hash table's O(1) as a design risk, and choose hash functions or structures that randomize the outcome.
7.11.2 The Expected Number of Probes
On the expected side, there is a classic result. Assuming the hash values behave like random numbers, the expected number of probes for an insertion with open addressing is
This is the average number of cells you have to inspect before you can insert an element (equivalently, the expected number of probes to search for an element that is not present). The proof is a standard theorem — it appears in the Cormen reference textbook (CLRS) for those who want to see it.
Why the formula is true. Count the probes one by one.
- The first probe is always made: that contributes 1.
- A second probe happens only if the first cell was occupied. Under the uniform-spread assumption, a random cell is occupied with probability , so the second probe happens with probability about .
- A third probe happens only if the first two cells were occupied: probability about .
- Continuing, the expected number of probes is the sum of the probabilities that each probe happens:
The last step uses the geometric-series identity , valid for . Each term is the probability that the first cells were all occupied, forcing the -th probe.
Numeric spot-checks. At the formula gives probes; at it gives probes; at it gives 10 probes. These numbers match the reference textbook's worked examples — half-full tables average two probes, and 90%-full tables average ten.
Notice what the formula says: as the load factor approaches 1, the expected probes grow without bound — . That is the mathematical reason the load factor must be kept below a constant like 0.75: it is not a style choice, it is what keeps open addressing efficient. (For searching for a key that is present, the standard companion result is probes — a slightly smaller number, since successful searches stop earlier on average.)
7.11.3 Assumptions and Scope
Assumption: The formula assumes uniform hashing — every probe sequence is equally likely, which double hashing approximates and linear/quadratic probing do not (their probe sets are smaller). Real linear-probed tables perform a bit worse than the formula predicts; the formula is the benchmark, not the exact number. Scope: The result needs : at the table is completely full, insertion cannot succeed, and the geometric series no longer converges. This is the same boundary that forces the load factor to stay below its bound — the formula and the policy are two views of the same limit.
7.11.4 Visual Intuition — The Explosion near 1
Picture a graph with the load factor on the horizontal axis from 0 to 1, and the expected number of probes on the vertical axis. The curve is flat near the left — at it is about 1.33 probes — and climbs gently through (2 probes) and (4 probes). Then, just before , the curve turns sharply upward and runs off the top of the graph — at it is already 10 probes, and it has no finite value at . The landmark is the vertical asymptote at : the curve never touches it, and the growth policy exists to keep the table far left of it. The one-sentence takeaway: the cost curve is tame until the last few percent of fullness, then explodes — the load-factor bound is the guardrail at the cliff.
7.11.5 Common Pitfalls
- Treating the worst case as if it were irrelevant theory. The case is real and reachable: all keys colliding turns every operation into a linear scan. For data that guarantees this, the professional answer is not a better hash function but no hash table at all.
- Quoting "hash tables are O(1)" without the qualifiers. The O(1) is the expected time under a good hash function and a bounded load factor. Both conditions are part of the claim.
- Using the formula at . is meaningless at and negative beyond it; the formula's domain is the table's operating region, which is exactly why the load factor is kept below 1 (preferably 0.75).
- Assuming the formula is exact for every open-addressing method. It is exact for uniform hashing; linear probing's cluster behavior makes it slightly pessimistic in practice, and quadratic probing's limited probe sets deviate from the ideal too.
7.11.6 Student Questions and Answers
Q: Why are we taking a prime number? Can you tell once again? A: Refer to the previous session — we did that with an example. A prime capacity spreads the hash values better and keeps the probe sequences of open addressing from collapsing onto too few cells. (The same reason returns in quadratic probing and double hashing.)
Q: If there is a collision for the value of the hash function, then what do we have to do? A: Use a collision handling method. The methods are: separate chaining — attach the element to the bucket cell as a linked list; and open addressing, under which we have linear probing (H(K) + 1 mod N, H(K) + 2 mod N, ... continuing circularly), quadratic probing (H(K) + i + i², i from 1 upward), and double hashing (two hash functions; on collision compute H'(K), and the probe is H(K) + i times H'(K)). Each has its own arithmetic; the concepts are the same.
Q: In the worst case, is search really O(n)? A: Yes — in the worst case, search, insertions, and removals on a hash table are O(n): that happens when all the keys inserted collide. That is the case you would not have noticed until now, because we are always told the hash table performs in O(1) — but that O(1) is under the assumption that the number of entries stays bounded by the capacity of the bucket array.
Exam note: the expected-probes formula and the worst case were both highlighted as results worth knowing; the proof lives in the reference book for those who want to go deeper. Remember the two numbers together: at , a missing key costs about 4 probes on average — a bounded constant — and the worst case, when every key collides, costs .
Recap + Bridge: Hashing is a tale of two numbers: the expected probes — constant when the load factor is bounded — and the worst case when all keys collide. The load factor is the knob between them. With hashing complete, the session shifts to the second half: binary search trees, where the same performance story (logarithmic when balanced, linear when skewed) is told by a different data structure.
Real-world connection: the expected-probes formula is why system designers quote "constant expected time" for hash lookups and why load-factor policies are tuned to fractions like 0.7–0.9 in production systems — every table from in-memory caches to database indexes chooses its bound on this curve. And the worst-case warning is the reason high-security systems use randomized hashing (a secret per-process seed) or trees for key lookups: when an attacker can engineer collisions, the O(1) promise is exactly what gets weaponized.
7.12 Binary Search Trees — Definition and Property
7.12.1 The BST Property
We shift from hashing to the second half of the session: binary search trees. A binary search tree (BST) is a binary tree that stores keys at its internal nodes and satisfies the following property. Let , , be three nodes such that is in the left subtree of and is in the right subtree of . Then
Formalize. Every node of a BST holds a key (the value that orders it), and the tree obeys one ordering rule:
- For any node , every key in the left subtree of is smaller than .
- Every key in the right subtree of is larger than or equal to .
In the formula, stands for any node in 's left subtree, for any node in 's right subtree, and for the key stored at node . The key point hiding in this definition: it talks about entire subtrees, not just children — and the rule applies recursively to every node, which is exactly the point students later miss in the exam question of Section 7.19.
In plain words: every key in the left subtree of a node is smaller than the node's key, and every key in the right subtree is larger than (or equal to) the node's key. The instructor's one-line restatement: the keys on the left of a node will be smaller than the node, and the keys on the right will be larger than the node. That's it — a binary search tree is a binary tree that satisfies this property. The ordering holds not just against the immediate children but against every ancestor: the property applies to whole subtrees, which is exactly the point students later miss in the exam question of Section 7.19.
The professor writes the left side with a strict "less than"; the standard textbook treatment writes , allowing equal keys to sit on either side. For distinct keys — the case in this session's examples — the two forms are identical, and the lecture's version is the one to use on the exam.
7.12.2 Inorder Traversal Visits Keys in Order
Because of the BST property, an inorder traversal — the traversal where the root sits in the middle, left subtree, then root, then right subtree — visits the keys in increasing order. For the example tree shown in the session (root 6; left child 2 with children 1 and 4; right child 9 with left child 8), the inorder traversal produced 1, 2, 4, 6, 8, 9: strictly ascending. This ordering fact is the engine behind rank (Section 7.18), successor and predecessor (Section 7.15), and the kth-smallest algorithm.
Intuition + analogy: An inorder traversal is like reading the keys as if the tree were a sorted filing cabinet. The rule "visit left, then root, then right" matches the property "left keys are smaller, right keys are larger": at every node you finish the smaller half, then the node itself, then the larger half — so the output is sorted without any sorting. The analogy breaks only when duplicate keys exist: the ≤ convention can place an equal key on either side, so the order of duplicates is not fixed.
7.12.3 The External-Node Convention
The trees in the material follow the Goodrich textbook convention: external nodes do not store items. The slides draw external-node squares at the bottom of the tree — these external nodes are not real data-bearing nodes, and the instructor was explicit: don't get confused by them, we are not going to store items in them, we ignore them. They exist only to keep the tree representation uniform. (Search and insertion will treat them as the empty positions where a search path ends.)
7.12.4 Assumptions and Scope
Assumption: The BST property assumes keys are totally ordered — for any two keys one of "less", "greater", "equal" holds — so that "go left" and "go right" are always well-defined. The examples use numbers, but any ordered type works (words, dates, IDs). Scope: The property itself says nothing about shape: a BST of the same keys can be bushy (height ) or degenerate (height ) depending on insertion order (Section 7.17). The claims of the next sections all assume the tree stays balanced; the property alone guarantees only correctness, never balance.
7.12.5 Visual Intuition — A Tree That Reads Left to Right
Picture the session's tree:
6
/ \
2 9
/ \ /
1 4 8
The BST property paints a picture: at every node, everything hanging to the left is smaller, everything to the right is larger. The landmark is the left-to-right sweep: if you shrink the tree down to one line, left-to-right, you get 1, 2, 4, 6, 8, 9 — the sorted order. The empty squares at the bottom are the external nodes: empty sockets that show where a search would stop or an insertion would land, holding no keys of their own. The one-sentence takeaway: a BST is a sorting machine shaped like a tree — read it left to right and the keys come out sorted.
7.12.6 Common Pitfalls
- Checking the property only against the immediate parent. The BST rule holds against every ancestor, not just the parent. A node can be larger than its parent and still violate the tree if it sits in the wrong side of a grandparent — the exact trap in the past exam question (Section 7.19).
- Mistaking external nodes for data. The external-node squares store nothing; treating them as nodes with keys or counting them in the key set is a misunderstanding of the convention.
- Believing the BST property forces balance. It does not: 1, 2, 3, 4 inserted in order produce a tree that is a straight line to the right, and the property holds perfectly at every node (Section 7.17).
- Forgetting that the property is recursive. It is not enough that each child obeys the rule; every subtree must obey it, which is why subtrees can be searched independently.
7.12.7 Student Questions and Answers
Q: Does the inorder traversal of a BST really visit the keys in increasing order? A: Yes — because of the BST property. The inorder traversal goes left, root, right at every node, and the left subtree always holds smaller keys than the root, which holds smaller keys than the right subtree. So the output is sorted ascending — in the example tree, 1, 2, 4, 6, 8, 9.
Recap + Bridge: A binary search tree is a binary tree whose keys obey one recursive ordering rule — left smaller, right larger — and that single rule makes the inorder traversal output sorted keys. Everything that follows — search, insert, delete, successor, rank — is built on this one property. The first operation we build on it: searching, which walks a single downward path and discards half the tree at each step.
Real-world connection: BSTs are the shape behind ordered maps and ordered sets in standard libraries (for example, the ordered containers in C++ and Java) and behind the sorted index structures of many databases. The property of this section is what lets those structures answer "is this key present?" and "what is the next key?" in logarithmic time — the exact operations the rest of this half of the session develops.
7.13 Searching in a BST
7.13.1 The Downward Path
To search for a key , trace a downward path starting at the root. At each node , compare with : if , the key — if present — must lie in the left subtree; if , it must lie in the right subtree; if equal, we have found it. One whole subtree is discarded at every step, and that is what makes the search fast.
Hook: A sorted list needs binary search to find a key in comparisons. The BST does the same thing with nothing but tree pointers — the ordering is stored in the shape of the tree. How does a single downward path reproduce the "halve the search space" trick?
The BST property is doing the work: because every left-subtree key is smaller than the node and every right-subtree key is larger, a single comparison at each node tells us which half of the remaining tree could possibly contain . Searching a BST is exactly the tree-shaped version of binary search on a sorted array: each step picks the left or right half and throws the other half away.
7.13.2 Worked Example — Find 4
Consider the tree with root 6, left child 2, right child 9, with 1 and 4 under 2 and 8 under 9. We want to find the key 4.
6
/ \
2 9
/ \ /
1 4 8
Step 1. Start at the root, key 6. Compare: . Since 4 < 6, 4 is definitely going to be in the left subtree of 6 (a BST guarantees this). Discard the right subtree (9 and 8) entirely.
Step 2. Move to the left child, key 2. Compare: . Since 4 > 2, 4 is definitely going to be on the right side of 2. Discard the left subtree (1).
Step 3. Move to the right child of 2, key 4. Compare: . Found.
Only two comparisons, each halving the remaining possibilities — no wasted scanning. Sense-check. Inorder order of the tree is 1, 2, 4, 6, 8, 9; the path 6 → 2 → 4 is exactly the way binary search would reach the third element of the sorted list.
7.13.3 The Recursive Algorithm and Its Analysis
The search algorithm is recursive and compact. With the current (sub)tree and its root node:
Algorithm — search(T, v, K):
- If is an external node, return "no such key".
- If , search in .
- Else if , return the element at .
- Otherwise (), search in .
The running time is
where is the height of the tree. Since this is a binary tree, the height is about for a balanced tree, and every search step traverses only one subtree — the property of the BST is exactly what makes the search logarithmic in the number of keys. The instructor noted that the full analysis of why search is was written out and proved in the earlier session on binary trees — the same proof applies here, because at each level only one branch is entered. This point was emphasized as really important; anyone still unsure was pointed back to that earlier proof.
7.13.4 Assumptions and Scope
Assumption: The claim assumes the tree is balanced — height . The search itself is always no matter the shape; the logarithm enters only when the tree is bushy. For a skewed tree of height , the same algorithm is (Section 7.17). Scope: The algorithm assumes keys are distinct enough for the comparisons to decide a single path, and it assumes an ordered key type. With duplicate keys under the convention, "go right on equal" must be fixed consistently so that searches and insertions agree.
7.13.5 Visual Intuition — Halving the Tree at Every Level
Picture the search for 4 on the tree above as a flashlight beam shining down from the root: at node 6 it lights only the left branch (the right branch goes dark), at node 2 only the right branch, and at node 4 the beam stops. At each level exactly one of the two branches remains lit, so the number of visited nodes equals the height of the tree — the depth of the beam, not the size of the tree. The landmark is the discarded subtrees: with 6 nodes, the search touched 3 and ignored 3. The one-sentence takeaway: a BST search is a single root-to-leaf beam, and its length — the height — is the entire cost.
7.13.6 Common Pitfalls
- Continuing to compare after finding a match. The search ends the moment ; there is no need to descend further, because subtrees cannot contain another copy unless duplicates are stored.
- Forgetting the external-node exit. If the beam reaches an external node (the empty square at the bottom), the key is not in the tree — the recursion must stop there, or it recurses forever.
- Quoting O(log n) for every tree. The correct statement is , and is only for a balanced tree; quoting the logarithm without the balance assumption invites errors on skewed examples.
- Searching both subtrees. The whole point of the BST property is that one comparison discards one entire side; checking both subtrees turns the search into a full tree walk.
7.13.7 Student Questions and Answers
Q: What is the time complexity of searching for an element in a BST? A: O(log n) — the height of the tree. Since it is a binary tree, we know it is log base 2 of n. Every step discards one whole subtree, so the search follows a single downward path of length equal to the height.
Recap + Bridge: Searching a BST follows one downward path, comparing at each node and discarding the other subtree, in — about steps for a balanced tree. Insertion reuses the very same path: the place where the search would fail is exactly where the new key is planted.
Real-world connection: BST search is the mechanism inside ordered-map lookups in standard libraries and database ordered indexes — the same "follow one path, discard half" idea that makes phone-directory lookup, autocomplete tries, and binary-search-adjacent structures fast on ordered data.
7.14 Inserting into a BST
7.14.1 The Insertion Procedure
To insert a new key (assume the key is not already in the tree): search for exactly as in Section 7.13. The search ends at a leaf position — an external node — where the key would live. Insert the new key at node , and expand into an internal node. In other words: follow the path the key would take, and the new node lands at the leaf position where the search stops.
Formalize. Insertion = failed search + expansion:
- — the new key.
- — the tree being modified.
- — the external node where the search for ended; it is the unique position where belongs, because every other position is ruled out by the comparisons along the path.
Why is that position correct? At every node on the path, the comparison decided which side must be on; when the path runs out of internal nodes, the remaining external node is the only slot that respects every comparison made. So the BST property is preserved automatically — nothing is rearranged, no rotations, no checks.
Intuition + analogy: Inserting into a BST is like placing a book into a sorted bookshelf by starting at the first shelf and always moving left or right according to the title: you never move a book that is already there, and the new book ends up at the single empty slot where it keeps the whole shelf sorted. The key difference from an array: no shifting — the tree simply grows one new leaf, so the cost is only the walk down.
7.14.2 Worked Examples — Insert 5 and Insert 10
Insert 5 into the tree from Section 7.13.2 (root 6, left 2, right 9; 1 and 4 under 2; 8 under 9):
6
/ \
2 9
/ \ /
1 4 8
Step 1. → go to the left subtree. Step 2. → go to the right subtree. Step 3. → go to the right of 4. Step 4. The position to the right of 4 is external → insert 5 there.
The tree now has 5 as the right child of 4, and the BST property holds: 5 > 4 and 5 < 6, so it sits correctly in 6's left subtree and in 2's right subtree.
Insert 10:
Step 1. → go to the right subtree. Step 2. → go to the right of 9. Step 3. The position to the right of 9 is external → insert 10 there.
Both insertions followed a single path from the root; nothing was rearranged. Sense-check. The inorder order grows from 1, 2, 4, 6, 8, 9 to 1, 2, 4, 5, 6, 8, 9, 10 — still strictly ascending, which is exactly what insertion must preserve. Insertion in a BST is always a downward search followed by placing the new node at the external position found.
7.14.3 Assumptions and Scope
Assumption: Insertion assumes the key is not already in the tree; with duplicates allowed, the convention "equal keys go right" must be chosen once and kept consistent with search, or a key could end up unreachable. The examples also assume the external-node convention: the new node is planted at the external-node square where the search path ended. Scope: Insertion cost is , the length of the search path — about on a balanced tree, on a skewed one. The procedure never balances the tree; the shape after many insertions depends entirely on the order in which keys arrive (Section 7.17).
7.14.4 Visual Intuition — Growing One New Leaf
Picture the tree as a mobile hanging from the root 6. Inserting 5 means walking down the wire to 6, then down the left wire to 2, then to 4, and clipping a new leaf to the right of 4; inserting 10 clips a new leaf to the right of 9. The visual landmark: the new node always hangs at the bottom — the tree never gains an internal slot, only a new dangling leaf at the end of an existing path. The one-sentence takeaway: insertion is "search, then grow" — the search path decides the spot, and the tree grows only at the bottom.
7.14.5 Common Pitfalls
- Trying to insert "in between" nodes. Insertion never places a key between existing nodes — the new node is always a leaf at the end of the search path. The in-between placement you might be thinking of is the deletion problem, which is why we next study inorder successor and predecessor.
- Rearranging the tree on insertion. No rotations or swaps are needed; the path comparisons already determine the unique correct position. Rearranging risks breaking the BST property.
- Forgetting that the search determines the path. Insertion without the search (for example, attaching at an arbitrary leaf) can violate the ordering; the comparisons are what guarantee correctness.
- Ignoring the growth pattern. Repeated insertions in sorted order keep adding rightmost leaves, slowly degenerating the tree toward a list (Section 7.17) — correct but increasingly slow.
7.14.6 Student Questions and Answers
Q: Is insertion always at a leaf node? A: Yes — you traverse the tree along the path the key would take, and the new node is inserted where the search ends, expanding that external node into an internal node. Insertion is not "in between" nodes. The in-between placement you might be thinking of is the deletion problem, which is why we next study inorder successor and predecessor.
Recap + Bridge: Insertion is a failed search: walk the key's path, plant the new node at the terminal external node, and the BST property holds automatically in time. Deletion is harder, because removing an internal node can leave a hole in the middle of the ordering — and filling that hole is exactly what inorder successor and predecessor are for.
Real-world connection: this grow-at-the-leaf insertion is how ordered containers accept new entries — databases insert index keys, phone books add subscribers, calendars add events — all by the same "walk and plant" rule. Because the shape is never corrected, real systems pair this insertion with a balancing scheme; the next sections show what can go wrong without one.
7.15 Inorder Successor and Predecessor
7.15.1 Definitions
The inorder traversal of a BST gives the keys in ascending order. Using that ordering:
- The inorder successor of a key is the smallest number which is larger than the key — the element just after it in the inorder traversal.
- The inorder predecessor of a key is the largest number which is smaller than the key — the element just before it.
A useful memory trick offered in the session: successor means large, so it lives in the right subtree; predecessor means small, so it lives in the left subtree. Within the right subtree the successor is the leftmost node; within the left subtree the predecessor is the rightmost node.
Hook: Deleting a node with two children leaves a hole that only one kind of value can fill. The two candidates — the node just before and the node just after the deleted key in sorted order — are exactly the inorder predecessor and successor. To delete correctly, we first have to find them.
7.15.2 The Rules for Locating Them
For a node :
Predecessor of (the largest key smaller than ):
- If has a left subtree, the predecessor is the maximum value in that left subtree — the rightmost child of the left subtree.
- If the left subtree does not exist, the predecessor is one of the ancestors: travel up using the parent pointer until you reach a node that is the right child of its parent; the parent of that node is the predecessor.
- If you reach the root without finding such a node, the key has no predecessor.
Successor of (the smallest key larger than ):
- If has a right subtree, the successor is the minimum value in that right subtree — the leftmost child of the right subtree.
- If the right subtree does not exist, travel up using the parent pointer until you reach a node that is the left child of its parent; the parent of that node is the successor.
- If you reach the root without finding such a node, the key has no successor.
The intuition behind the ancestor rules: a predecessor must be smaller than the key. If there is nothing smaller in the left subtree, the answer is the first ancestor that is "below-left" of the key — i.e., where the key hangs off its right side. The successor rule is the mirror image.
7.15.3 Worked Examples
Tree 1 (root 15; left subtree 10 with children 8 and 12; right subtree 20 with children 16 and 25). Inorder: 8, 10, 12, 15, 16, 20, 25.
15
/ \
10 20
/ \ / \
8 12 16 25
- Inorder predecessor of 8: 8 has no left subtree, so travel up. 8 is the left child of its parent — keep going. 10 is the left child of 15 — keep going. 15 is the root — stop. Predecessor of 8 does not exist. (8 is the leftmost key — nothing precedes it in the sorted order.)
- Inorder predecessor of 20: 20 has a left subtree {16}; the maximum there is 16. Predecessor of 20 = 16.
- Inorder predecessor of 12: 12 has no left subtree. Travel up: 12 is the right child of its parent (10), so the parent is the predecessor: Predecessor of 12 = 10.
Tree 2 (root 20; left subtree 8 with right child 12, 12 with right child 14; right subtree 30 with left child 25). Inorder: 8, 12, 14, 20, 25, 30.
20
/ \
8 30
\ /
12 25
\
14
- Inorder predecessor of 20: left subtree {8, 12, 14}; the rightmost child is 14. Predecessor of 20 = 14 (this is the maximum element smaller than the key).
- Inorder predecessor of 14: 14 has no left subtree. Travel up: 14 is the right child of 12, so 12 is the predecessor. Predecessor of 14 = 12.
- Inorder predecessor of 4 (hypothetically the left child of 8): no left subtree; travel up — 4 is the left child of 8, 8 is the left child of its parent 20, 20 is the root — so 4 has no predecessor. (You go only up to the root.)
- Inorder successor of 8: 8 has a right subtree {12, 14}; the leftmost child is 12. Successor of 8 = 12. (In Tree 1, the successor of 8 was 10 — the leftmost of its right subtree — showing the same rule in both trees.)
- Inorder successor of 14: 14 has no right subtree. Travel up: 14 is the right child of 12 — not a left child, keep going; 12 is the right child of 8 — keep going; 8 is the left child of 20 — yes. The parent of such a node (the parent of 8) is the successor: Successor of 14 = 20.
Sense-check. In Tree 2, inorder is 8, 12, 14, 20, 25, 30 — the predecessor of 20 is 14 and the successor of 14 is 20: the two rules are exact inverses, as they must be for neighboring keys.
7.15.4 Assumptions and Scope
Assumption: The ancestor rules assume every node carries a parent pointer (or that the walk can otherwise climb). Without parent pointers, the subtree rules still work, but the ancestor case requires remembering the last "turn left" node during a root-to-node search instead. Scope: The rules locate neighbors in the inorder (sorted) order; the successor of a node is not necessarily its parent, its sibling, or anything visually adjacent. And a key can have no successor (it is the largest) or no predecessor (it is the smallest) — the walk stops at the root, and "no such node" is a valid answer.
7.15.5 Visual Intuition — The Two Mirrored Walks
Picture Tree 2: successor and predecessor are mirror images. To find the successor of 14, walk right one step... there is no right child, so climb the left spine: 14 → 12 (a right child — not it), → 8 (a left child — yes), and the answer is the parent of 8, which is 20. The climb rule reads like a signpost: keep climbing while you are coming up from the right; the first time you come up from the left, that parent is the answer. The landmark is the direction of the final climb — "came up from the left" for successor, "came up from the right" for predecessor. The takeaway: successor climbs until the node is a left child; predecessor climbs until the node is a right child.
7.15.6 Common Pitfalls
- Stopping the ancestor climb too early. You must climb until you find a node that is the right child of its parent (for predecessor) — a left-child node is not the answer, and stopping there gives a wrong or missing predecessor.
- Confusing which direction each rule climbs. Successor climbs until a left child; predecessor climbs until a right child. Swapping the two directions is the classic slip — remembering "successor = large = left child of parent" helps.
- Taking the root as the answer when climbing fails. Reaching the root without meeting the condition means the key has no predecessor or successor (it is the extreme key) — not that the root is the answer.
- Forgetting the subtree case takes the extreme child. Within the left subtree the predecessor is the rightmost (maximum) node, not the leftmost — within the right subtree the successor is the leftmost (minimum) node. Reversing the extremes is a frequent mistake.
7.15.7 Student Questions and Answers
Q: How do you find the predecessor of 5 when 5 has no left subtree? A: Travel up using the parent pointer until you find a node that is the right child of its parent; the parent of such a node is the predecessor. In the tree with 3 as root, 6 as its right child, and 5 and 8 under 6: 5 has no left subtree, so go up — 5 is the left child of 6, not a right child, keep going — 6 is the right child of 3 — yes. The parent of 6 is 3, so the predecessor of 5 is 3.
Q: Is the successor of a key just the smallest number larger than it? A: Yes. Successor of a key is the smallest number which is larger than the key, and predecessor is the largest number which is smaller than the key. Digest those two statements — they drive both the subtree rule and the ancestor rule.
Recap + Bridge: The successor is the leftmost node of the right subtree, or the first ancestor reached by climbing until a left child; the predecessor is its mirror image. These two neighbors are exactly the values that can take a deleted node's place — which makes them the heart of deletion, the next operation.
Real-world connection: successor and predecessor queries are what ordered structures use to answer "what is the next larger key?" — the operation behind range queries in databases, floor/ceiling lookups in ordered sets, and navigation between adjacent entries in sorted indexes.
7.16 Deleting from a BST
7.16.1 Case 1 — No Children
Deleting a node with no children is trivial: just delete the node. For example, deleting 18 from the session tree — 18 has no children — so simply remove it. Nothing else changes.
Hook: Search, insert, and find all ride on one downward path. Delete is the first operation where removing a node can leave a hole — and the harder the case, the more cleverly the hole must be filled.
In the external-node convention, deleting a leaf means removing the internal node and letting its two external nodes collapse back into one — the tree simply shrinks by one leaf, and every remaining subtree keeps its ordering.
7.16.2 Case 2 — One Child
Deleting a node with one child: remove the node and replace it with its child. There is nothing to check — the child simply takes the deleted node's place, and the BST property is preserved because the child was already in the correct subtree.
Two worked examples: delete 25, which has a single child 30 → replace 25 with 30. Delete 4, which has a single child 5 → replace 4 with 5. (In the material's drawings, the empty rectangles around such nodes are just the external-node convention of Section 7.12.3 — if the rectangles confuse you, ignore them.)
Why is no check needed? The child was already a valid member of the deleted node's subtree: everything in the child's left subtree was smaller than the child, everything in its right subtree larger, and the child itself was already correctly placed relative to the deleted node's parent. Splicing the child up into the parent's slot keeps every ancestor ordering intact.
7.16.3 Case 3 — Two Children
This is where the successor/predecessor concept pays off. To delete a node with two children, replace that node with its inorder successor or its inorder predecessor — whichever you choose, the result is still a BST. Why replace at all? To restore the BST property: the replacement node is the one value that can take the deleted node's place without violating the ordering.
Formalize. To delete node with two children:
- Find the inorder successor of (leftmost node of 's right subtree) or the inorder predecessor (rightmost node of 's left subtree).
- Copy the key of (or ) into , and delete the node (or ) from its original position.
Why either candidate works: the successor is the smallest value larger than , so every key in 's right subtree is still it, and every key in 's left subtree is still smaller — the two subtree orderings are both preserved. The predecessor is symmetric (the largest value smaller than the key). And crucially, the replacement node has at most one child — the leftmost node of a subtree has no left child — so its own removal falls into Case 1 or Case 2, never into Case 3 again.
Worked example — delete 20 (which has two children). Using the inorder predecessor: by definition the predecessor is the rightmost child of the left subtree; in the session tree that is 19. Delete 20 and replace it with 19. Using the inorder successor (the leftmost child of the right subtree) would have given 30 — also correct. Either choice keeps the tree a BST.
Worked example — delete 3 (with two children). Using the inorder successor: the right subtree of 3 has leftmost child 5, so delete 3 and replace it with 5.
Sense-check. In both cases the replacement is the sorted neighbor of the deleted value — 19 is the largest value below 20, 5 is the smallest value above 3 — so the inorder order of the tree stays sorted, and the BST property holds at every ancestor automatically.
The instructor's explanation of why this works: the predecessor is the largest value smaller than the key, and the successor is the smallest value larger than the key — exactly the two candidates that can occupy the deleted node's position and keep every subtree ordered. So case 3 = delete, replace with successor or predecessor (follow one convention), and the BST property is automatically restored.
7.16.4 Assumptions and Scope
Assumption: The case-3 procedure assumes the replacement is found by the rules of Section 7.15 (subtree extreme, or parent climb) and that one convention — always successor or always predecessor — is followed consistently. Mixing conventions per deletion is still correct for the BST property but makes hand-tracing error-prone. Scope: Deletion cost is : the case-3 steps are one downward walk to find the replacement plus a bounded splice. Every operation — search, insert, delete, successor, predecessor — traverses only one side of the tree, so each stays at the height of the tree: for a balanced BST, for a skewed one.
7.16.5 Visual Intuition — Splicing Out a Node
Picture the three cases as three kinds of surgery. A leaf is plucked away — the tree loses one twig. A one-child node is replaced by its child — the child slides up into the parent's slot like a branch being grafted upward one level. A two-child node is the interesting one: instead of removing it (which would open a hole with no obvious filler), we copy the value of its neighbor in sorted order (the successor, the smallest thing to its right — or the predecessor, the largest thing to its left) into the node, then delete that neighbor, which is a leaf or near-leaf. The visual landmark: the deleted value disappears, but the node's shape stays occupied by its sorted neighbor — the tree keeps its structure and only the labels change. The takeaway: case 3 is "steal a neighbor's value, then delete the neighbor, never the two-child node itself".
7.16.6 Common Pitfalls
- Removing the two-child node itself. Deleting the node and trying to reconnect both children creates a hole that cannot be filled by both subtrees at once — the sorted order breaks. The correct move is to replace the value and delete the neighbor.
- Worrying that the replacement node has children. The inorder successor is the leftmost child of the right subtree and the inorder predecessor is the rightmost child of the left subtree, so the replacement node has at most one child — and often none. Don't confuse yourself with too many things; just follow one convention (successor or predecessor) consistently and the deletion works out.
- Choosing a random replacement. Only the successor or the predecessor can take the deleted node's place; any other node in the tree either is not larger than all of the left subtree or not smaller than all of the right subtree.
- Forgetting the ancestor checks after deletion. After splicing, verify (or trust the proof) that every ancestor still satisfies the BST property — the reason the replacement works is precisely the ordering guarantee, not luck.
7.16.7 Student Questions and Answers
Q: When we delete a node with two children, do we have to worry that the replacement node itself has children? A: The inorder successor is the leftmost child of the right subtree and the inorder predecessor is the rightmost child of the left subtree, so the replacement node has at most one child — and often none. Don't confuse yourself with too many things; just follow one convention (successor or predecessor) consistently and the deletion works out.
Q: Does deletion keep the complexity at O(log n)? A: Yes. Whatever we do — search, insert, delete, successor, predecessor — we are traversing only one side of the tree, one path from the root. So every operation stays at the height of the tree, O(log n) for a balanced BST. There is no doubt in that, because we are always traversing only one side.
Q: When we delete 3, isn't the predecessor of 5 equal to 3? A: Yes, correct — the predecessor of 5 is 3. That is the reverse of the deletion: the same successor/predecessor rules apply symmetrically. Finding the predecessor of 5 uses the ancestor rule — 5 has no left subtree, so we travel up until a node that is the right child of its parent, and the parent of that node is the predecessor.
Recap + Bridge: Deletion has three cases — pluck a leaf, splice up a single child, or replace a two-child node's value with its successor or predecessor and delete that neighbor. Every case costs one downward path, so all BST operations share the same cost law: , logarithmic when balanced. And "when balanced" is the open question: the next section shows how badly insertion order can break that assumption.
Real-world connection: this delete procedure is the exact code path in ordered map libraries when a user removes a key — the "steal the successor's value" trick keeps such containers correct with minimal restructuring, and it is the reason balanced variants (AVL, red-black) can limit themselves to a handful of rotations after deletion rather than rebuilding the tree.
7.17 BST Performance — Balanced Trees and Skewed Trees
7.17.1 The Skewed Tree
The claims of the previous sections assume the tree stays balanced. The BST property does not force balance — insertion order does. If the keys are inserted in sorted order — 1, 2, 3, ... — each new key is larger than all previous ones, so each insertion goes to the rightmost position, and the tree ends up as a skewed tree: every node has one child, and the tree is really a list in disguise. The height becomes instead of , and every operation degrades to
Hook: The BST property was supposed to buy logarithmic search. But the same property that makes inorder output sorted also allows a tree that is a straight line — where "search" is just "walk the list". When does the tree quietly turn into a list?
The answer is the insertion order. The BST property says nothing about shape: it only says where a key may sit relative to another. Inserting keys in ascending order is the worst possible case — every new key is larger than everything present, so every insertion descends to the far right and hangs a new rightmost leaf, building a chain of nodes with height .
Worked example — inserting 1, 2, 3, 4, 5 in order. 1 becomes the root. 2 > 1 → right child of 1. 3 > 2 → right child of 2. 4 > 3 → right child of 3. 5 > 4 → right child of 4. The result:
1
\
2
\
3
\
4
\
5
The tree has height 5 for 5 keys — a list with right-pointing arrows. Searching for 5 compares against 1, 2, 3, 4, then 5: five comparisons, i.e. . Sense-check. Inserting the same keys as 3, 1, 5, 2, 4 (a balanced order) would give height 2 for the same 5 keys — the keys are identical, the shapes are not; insertion order alone decides.
7.17.2 Complexity in Both Worlds
- Balanced BST: search, insert, delete, successor, predecessor all in — height of the tree, log base 2 of .
- Skewed BST (sorted insertions): all operations in — height equals the number of keys.
| Operation | Balanced BST | Skewed BST |
|---|---|---|
| Search | ||
| Insert | ||
| Delete | ||
| Successor / Predecessor | ||
| Inorder traversal (all keys) |
The one-sentence rule for choosing: a BST with random or interleaved insertion order gives logarithmic behavior; a BST fed sorted (or reverse-sorted) data degenerates to the speed of a linked list — which is exactly why balanced variants exist.
The instructor's remark: BST performance can be skewed like this, and the fix belongs to the balancing material that follows the basic BST discussion. For the purposes of this session, the balanced analysis is what carries the claims, and the skewed case is the warning that insertion order matters.
7.17.3 Visual Intuition — The Tree That Lies Down
Picture the balanced tree as a triangle: 7 nodes, three levels, every search path about three steps. Now picture the same keys inserted in sorted order: the triangle collapses into a diagonal staircase — each node has one right child and no left child, so the "tree" is drawn as a single diagonal line. The landmark is the left subtrees: in the skewed tree every left subtree is empty, which is the tell-tale of sorted insertion. The one-sentence takeaway: the height of the tree is the ruler of all BST costs, and insertion order is the hand that bends the ruler.
7.17.4 Assumptions and Scope
Assumption: The numbers assume the tree stays balanced — height about . Nothing in the BST operations of this session enforces that; it is a property of the insertion sequence (or of an added balancing mechanism, covered in the balancing material). Scope: The skewed case is not a bug in the algorithms — every operation is still correct; only the speed collapses. And skew is not limited to ascending order: any sequence that keeps adding the largest (or smallest) remaining key, or builds a left-only chain, produces the same degeneration.
7.17.5 Common Pitfalls
- Believing the BST property guarantees balance. It guarantees only ordering. The same property that sorts the inorder output also permits a straight-line tree; balance comes from insertion order or from balancing algorithms, never from the property itself.
- Quoting O(log n) for "a BST" without qualification. The correct phrasing is O(h), which is O(log n) for balanced trees and O(n) for skewed ones. On an exam, the height of the given tree decides the answer.
- Forgetting that the degenerate case is easy to hit. Sorted input is not exotic — reading a log file, appending timestamps, or inserting IDs in order all produce it. Production systems do not rely on luck: they balance.
- Thinking the operations themselves change. Search, insert, and delete are the same code in both worlds; the height is what differs, and it alone determines the cost.
Recap + Bridge: A BST's performance is a race between balance and insertion order: balanced trees give for every operation, while sorted insertions skew the tree into a list and give . The operations we have built — search, insert, delete, successor, predecessor — are all . The kth-smallest algorithm, up next, rides the same inorder order — and then we use everything on a real past exam question.
Real-world connection: the skewing warning is the direct reason standard libraries never ship plain BSTs — the ordered containers in C++, Java, and Go use self-balancing trees (red-black, AVL, or treaps) so that no insertion order can degrade them. Database indexes face the same threat: time-ordered keys (timestamps, auto-increment IDs) are exactly the sorted-insertion pattern that would skew a plain BST, so real indexes use balanced or logarithmic structures.
7.18 The Kth Smallest Element in a BST
7.18.1 Rank — Position in the Inorder Traversal
The rank of an element is its position in the inorder traversal. In the session's example tree (keys 2, 6, 7, 8, 10, 15, 18, 20 in inorder), rank of 2 is 0, rank of 6 is 1, rank of 15 is 5, rank of 20 is 7 — counting positions starting at zero. Because the inorder traversal is sorted (Section 7.12.2), the element of rank is exactly the -th smallest element. This is the concept the kth-smallest algorithm is built on.
Hook: "Give me the 4th smallest element" sounds like a sorting job: sort everything, pick the 4th. But the BST stores the sorted order in its shape — so with a clever walk, the answer comes back in without sorting at all. The trick is one number: how many keys hang in each left subtree.
7.18.2 The Algorithm
Purpose. Find the element with a given rank in sorted order — the kth smallest key — without building the sorted list.
Inputs & Outputs. Input: the tree and a rank (1-based here: the smallest element has ). Output: the node (or key) at that rank.
Steps. The complete algorithm works top-down. At each node, let be the number of elements in the left subtree, and is the rank of the node itself (all left-subtree elements come before it in the inorder order). For a desired rank :
- If , the current root is the kth smallest — return it.
- Else if , the kth element must lie in the right subtree: set (subtract the elements already counted), and move to the right child.
- Otherwise (), move to the left child.
The reasoning: the left-subtree count tells us how many elements are smaller than the root. If we are looking for something with a larger rank than that count, the answer cannot be in the left subtree or at the root — it is in the right subtree, at a rank reduced by everything we just skipped.
Complexity & Cost. Each step descends one level, so the cost is — about on a balanced tree. The only extra requirement: each node must know its left-subtree size (a standard augmentation; once stored, the counts never slow the walk down).
7.18.3 Worked Example — The 4th Smallest Element
The tree: root 20; left child 10, right child 40; under 10: 6 (with children 2 and 8, where 8 has right child 7) and 15 (with right child 18); under 40: 30 (with children 25 and 35). The full inorder traversal is 2, 6, 7, 8, 10, 15, 18, 20, 25, 30, 35, 40 — 12 elements. We want the 4th smallest, so .
Iteration 1. Root is 20. Left subtree elements: 7 (10, 6, 15, 2, 8, 7, 18). Check: is ? No. Is ? No. So take the else branch: root = root.left = 10.
Iteration 2. Root is 10. Left subtree elements: 4 (6, 2, 8, 7). Check: is ? No. Is ? No. Else branch: root = root.left = 6.
Iteration 3. Root is 6. Left subtree elements: 1 (just 2). Check: is ? No. Is ? Yes — the moment this condition is satisfied we change the traversal to the right: , and root = root.right = 8.
Iteration 4. Root is 8. Left subtree elements: 1 (just 7). Check: is equal to ? Yes. The current root is the kth node → the answer is 8.
Sanity check against the inorder traversal: 2 (1st), 6 (2nd), 7 (3rd), 8 (4th) — the fourth smallest element is 8. ✓
The instructor's insight: you do not need to memorize this algorithm by heart. It is only built on the BST property — the number of left elements tells you how many elements are smaller than the node, so comparing with that count tells you which side of the tree the answer is on. Understand that, and the algorithm writes itself.
7.18.4 Assumptions and Scope
Assumption: The algorithm assumes the tree stores, at each node, the number of elements in its left subtree (or is augmented with subtree sizes). Without that count, deciding the branch would require counting, which costs per step. The session's traces supply the counts from the drawn tree. Scope: The 1-based convention ( is the smallest) is the one used in the trace; rank in Section 7.18.1 is 0-based (rank 0 = smallest). The exam question will state the convention it wants — "trace the algorithm" or "give the algorithm" — and the two must not be mixed.
7.18.5 Visual Intuition — The Ruler of the Left Counts
Picture the tree as a ruler where the left-subtree count at each node is a milestone: at node 20 the milestone says "7 elements stand before me", at node 10 it says "4", at node 6 it says "1". The walk for reads these milestones like a game of "hotter, colder": at 20, the milestone (7) is too big, so the answer is deeper; at 10, the milestone (4) equals the current , and since is false we go left; at 6, the milestone (1) is smaller than , so we subtract and swing right; at 8, the milestone (1) makes the updated equal . The landmark is the subtraction step: the only moment the walk turns right. The takeaway: the left counts are a running scoreboard, and the answer is the node where the scoreboard finally equals .
7.18.6 Common Pitfalls
- Confusing 0-based rank with 1-based k. Rank counts from 0; the kth-smallest algorithm counts from 1. The smallest element has rank 0 but — mixing the two gives an off-by-one error.
- Forgetting to subtract on the right turn. When moving right, must shrink by (the left subtree and the root); moving right with the original double-counts everything already passed.
- Checking only one condition. The algorithm needs the full three-way decision — equal, greater, or smaller — at every node; skipping the equal check turns "kth smallest" into a search for a specific key.
- Choosing inorder traversal when the question asks for the algorithm. The question will state explicitly which is expected — "trace the algorithm" or "give the algorithm". If it says give the algorithm, write the algorithm; if it says trace, trace it step by step with the given tree. Don't guess which one is wanted.
7.18.7 Student Questions and Answers
Q: Should we use the inorder traversal or the algorithm for the kth-smallest question? A: The question will state explicitly which is expected — "trace the algorithm" or "give the algorithm". If it says give the algorithm, write the algorithm; if it says trace, trace it step by step with the given tree. Don't guess which one is wanted.
Exam note: the kth-smallest question was actually asked in one of the earlier papers — the instructor confirmed "this is a question which I asked in the paper". Expect a variant: find the 4th smallest, find the kth smallest, trace the algorithm, or give the algorithm.
Recap + Bridge: The kth-smallest algorithm walks the tree comparing the target rank with left-subtree counts — left if the count is too big, subtract and go right if it is too small, return when they match — in . That milestone-counting skill is also exactly what the next topic needs: recognizing which nodes can and cannot sit where, when a given insertion order builds a tree.
Real-world connection: the kth-smallest operation is a "select" query — the same primitive used to find percentiles and medians in ordered data structures, from database query engines computing median salaries to analytics finding the 95th-percentile latency of a service. Ordered containers with subtree-size augmentation answer such queries in logarithmic time, which is why median/percentile lookups stay fast even on huge datasets.
7.19 Past Exam Question — Which BST Does an Insertion Order Produce?
7.19.1 The Question and the Common Mistake
A past exam question gives an insertion order and four candidate BSTs: "suppose the keys are inserted into a binary tree in this order — which of the following is the BST that is formed?" The catch is the phrase inserted in this order: the first key becomes the root, and every later key is placed by the search-path rule of Section 7.14.
Hook: The question sounds like a formality — insert six keys, pick the right tree. Yet most of the class picked the wrong option. The mistake is not arithmetic; it is a misunderstanding of the word "BST".
The insertion order in the example: 55, 63, 31, 17, 22, 40. So 55 is the root; 63 goes right of 55; 31 goes left of 55; 17 goes left of 31; 22 goes right of 17; 40 goes right of 31.
Most students picked the wrong option — one of the distractors showed 31 and 40 sitting in the right subtree of 55. The instructor's correction: 31 and 40 are smaller than 55, so they can never appear on the right-hand side of 55. The classic mistake — flagged explicitly — is checking only the immediate parent instead of all ancestors. You should not check only the immediate element: a BST requires every left-subtree key to be smaller than the node and every right-subtree key to be larger, against all ancestors, not just the parent. 40 is larger than 31 (its parent) and could sit right of 31 — but it still sits inside the left subtree of 55, which is illegal.
The ancestor rule. When validating (or building) a BST, every node must respect the ordering of every ancestor, not just its parent. Concretely: a node in the left subtree of the root must be smaller than the root and respect each intermediate node on the way down. The insertion rules produce such trees automatically; the distractors in exam options violate them by design — and the violation is only visible when you check the full path, not the parent alone.
7.19.2 The Correct Tree
The tree built by the insertion order 55, 63, 31, 17, 22, 40:
- 55 → root.
- 63 > 55 → right child of 55.
- 31 < 55 → left child of 55.
- 17 < 31 → left child of 31.
- 22 > 17 and 22 < 31 → right child of 17.
- 40 > 31 and 40 < 55 → right child of 31.
55
/ \
31 63
/ \
17 40
\
22
Verification. Every subtree check passes: all keys in the left subtree of 55 (31, 17, 22, 40) are smaller than 55, all keys in its right subtree (63) are larger, and the same holds recursively — 31's left subtree {17, 22} is all smaller than 31, its right subtree {40} is larger. The distractor fails the ancestor check: its left subtree of 55 contains values in its right positions that violate the global ordering — 40 right of 31 is fine locally, but it sits inside 55's left subtree, and a left-subtree key must be smaller than the root.
Sense-check. Reading the correct tree in inorder gives 17, 22, 31, 40, 55, 63 — the six inserted keys in sorted order. Any BST built from these keys must produce exactly this inorder sequence; a tree that cannot is not a valid BST.
7.19.3 Visual Intuition — The Global Fence
Picture the root 55 as a fence: everything planted to its left must stay on the left side of the fence, everything to its right on the right side — no exceptions, no matter how deep. The distractor's picture shows 31 and 40 planted on the right of the fence: locally 40 is happy right of 31, but globally it crossed the fence. The visual landmark is the deepest leaf of a subtree: it must still respect the fence at the top. The one-sentence takeaway: in a BST, every node obeys not its parent but its whole ancestry — the tree is a stack of fences, and the top fence is the strictest one.
7.19.4 Common Pitfalls
- Checking only the immediate parent. 40 > 31 makes "right of 31" look correct — but 40 < 55 is violated because 40 sits in 55's left subtree. The ancestor check is the whole question, and the exam distractor is built exactly on this slip.
- Reading the tree instead of building it. The question says "inserted in this order" — the first key is the root and each later key is placed by the search path. Guessing from the set of keys (or from the sorted order) picks a different, valid-looking tree that the insertion order never produces.
- Forgetting that every subtree must satisfy the property. A child pair can look fine while the grandparent ordering is violated; validation must be recursive, and building must follow the search path at every step.
- Treating "larger than parent" as "larger than everything relevant". The comparisons accumulate along the path: 22 > 17 (parent) and 22 < 31 (ancestor) — both comparisons decide the placement, not one of them.
7.19.5 Student Questions and Answers
Q: Which of the four trees is the BST formed by inserting 55, 63, 31, 17, 22, 40 in that order? A: The tree with 55 as root, 63 on the right, 31 on the left, 17 left of 31, 22 right of 17, and 40 right of 31. Option 2 (which most people chose) is wrong because 31 and 40 came in the right subtree of 55, which is not allowed — you must check all ancestors, not just the immediate parent. Most of the class wrote the second option; that is exactly the trap.
Recap + Bridge: A given insertion order builds exactly one tree — the first key is the root and every later key follows its search path — and a correct tree must pass the ancestor check at every node, not just the parent check. The same "walk from the root and compare with all ancestors" habit is what makes the next topic trivial: finding the least common ancestor of two keys is just a matter of noticing where their two paths split.
Real-world connection: this exam question models a real correctness requirement: when a database or application reconstructs a BST (for example, rebuilding an ordered index from a sorted log of inserts), the reconstruction is trustworthy only if every node passes the full-ancestor check. The distractor's flaw — a locally fine placement that violates a distant ancestor — is the same class of bug that slips into hand-written tree code and breaks range queries later.
7.20 The Least Common Ancestor in a BST
7.20.1 The Idea
Given a BST and two values and , the least common ancestor (LCA) is the lowest (deepest) node that is an ancestor of both. A node counts as its own ancestor. In a BST the LCA can be found by a single downward walk: at each node, if both values are smaller than the node, both are in its left subtree; if both are larger, both are in its right subtree; the first node where the two values split — one on each side, or one equal to the node — is the LCA.
Formalize. For a node with key , the walk applies one of three rules:
Why the third rule is correct: if the two values lie on different sides of (or one equals ), then every node below can be an ancestor of at most one of them — one of the two paths would have to leave in the other direction. So no deeper node can be a common ancestor, and is the deepest (least) one.
Hook: "Find the node where two family members' family trees meet" — for BSTs this question has a one-walk answer that never backtracks. The trick: the two search paths from the root are identical until the split, so the LCA is simply where they stop agreeing.
7.20.2 Worked Examples
Use the tree with root 20; left child 8; 8's right child 12; 12's children 10 and 14; 20's right child 22.
20
/ \
8 22
\
12
/ \
10 14
- LCA of 10 and 14. Start at 20: both 10 and 14 are smaller — descend left to 8. Both are larger than 8 — descend right to 12. Here 10 and 14 split: 10 goes left, 14 goes right. Both live under 12, and 12 is the deepest node that is an ancestor of both → LCA(10, 14) = 12.
- LCA of 14 and 8. Start at 20: both smaller — descend left to 8. Here the values split: 8 equals the node itself. 8 is itself an ancestor of 14 (8 → 12 → 14), and no node deeper than 8 is an ancestor of 8 itself → LCA(14, 8) = 8. It cannot be 20, even though 20 is also a common ancestor — 20 is not the least common ancestor. 14 has ancestors 12 and 8, and 8 is the deepest one that is also an ancestor of 8 (itself).
- LCA of 10 and 22. Start at 20: 10 is smaller, 22 is larger — they split right at the root → LCA(10, 22) = 20.
Sense-check. In each case, the answer is the first node where the two search paths diverge: 12 for the two children of 12, 8 when one of the values is the node itself, 20 for a left-branch and a right-branch value.
7.20.3 Assumptions and Scope
Assumption: The single-walk rule assumes the two values exist in the tree (or that a missing value is handled before the walk). The BST ordering is what lets the walk decide "both left" or "both right" with one comparison; without a BST, finding the LCA needs parent pointers and path marking. Scope: The LCA is unique — there is exactly one deepest common ancestor. "A node counts as its own ancestor" is part of the definition, not a convention you may drop; without it, LCA(14, 8) would be undefined instead of 8.
7.20.4 Visual Intuition — The Fork in Two Paths
Picture two ants walking from the root toward keys 10 and 14. For a while they follow the same branches — 20, then 8 — until they reach 12, where one ant turns left and the other turns right: the fork in the road is the LCA. The landmark is the first fork: any common ancestor above the fork (8, 20) also covers both ants, but the fork is the deepest place that does. When one ant's target is on the fork itself (LCA(14, 8)), the fork is simply that node. The takeaway: trace the shared path; the LCA is where the shared path ends.
7.20.5 Common Pitfalls
- Reporting a shallower common ancestor. 20 is a common ancestor of 14 and 8, but not the least one — the walk must continue past any node where both values stay on the same side. Stopping early at the first common ancestor gives the wrong (too shallow) answer.
- Forgetting that a node is its own ancestor. LCA(14, 8) = 8 relies on this; students who exclude self-ancestry look for a deeper node that does not exist and answer 12 or 20 incorrectly.
- Descending when the values split. Once the two values sit on different sides of the current node (or equal it), the walk is over — descending further can never reach a node that is an ancestor of both.
- Applying the rule to non-BST trees. The "both smaller, both larger" test is powered by the BST ordering; on a general tree the LCA needs a different method (for example, marking ancestors from one node and climbing from the other).
7.20.6 Student Questions and Answers
Q: Can a node be considered its own ancestor when computing the least common ancestor? A: Yes — when we are given multiple elements, we can consider it that way. LCA(14, 8) is 8 because 8 itself is an ancestor of 14, and no deeper node is an ancestor of both.
Exam note: LCA problems are exam questions — the instructor gave the examples above and pointed to complete solutions in the material. Students were told explicitly that skipping these solutions means being unable to solve similar questions in the exam; going through the course PPTs alone will not be enough.
Recap + Bridge: The LCA of two keys is found by walking from the root until their paths split — one comparison per level, total — with a node counting as its own ancestor. With hashing and BSTs both covered and one past paper worked, we now walk through the exam questions themselves: the exact shapes, traps, and formulas the paper tests.
Real-world connection: LCA queries are a workhorse of tree algorithms beyond exams — they power "nearest common ancestor" computations in phylogenetic trees (biology), in file-system diff tools (two branches of a directory tree), and in version-control merge-base computations, where the LCA of two commit histories decides the common ancestor commit to merge from. The BST version's single downward walk is the model for these tree algorithms: follow the structure, stop at the fork.
7.21 Past Paper Walkthrough
The instructor opened a past exam paper and walked through its questions, item by item, to show the exam's shape. Several of these are scenario-based — the paper in question was set for an offline closed-book sitting, and the same skills appear in the current (online) format with different framing.
7.21.1 Choosing between Two Algorithms for Small Inputs
The first question: you are given two algorithms for solving a problem of size at most 7. Algorithm one takes steps; algorithm two takes steps. Which algorithm do you choose, and why?
Hook: The trap is baked into the question's shape: it asks about small inputs on purpose. Choosing by asymptotic reputation here costs marks — the asymptotically "better" function is the slower one at the given size.
The trap: everybody tries and ends up with zero marks — because for , the computation goes the other way. Compute the actual numbers:
Worked example — size .
- Algorithm one: steps.
- Algorithm two: steps.
- Compare: .
So for the given size (at most 7), algorithm two is the choice: 32 < 49. The reason is not that algorithm two is "asymptotically better" — for large , beats overwhelmingly — it is that at the crossover has not happened yet. Sense-check. The crossover point: occurs between (49 vs 32) and (64 vs 64): at both give 64, and beyond 8 the polynomial wins. So "size at most 7" is exactly the regime where the exponential is still cheaper.
The conceptual point is the one from the asymptotic-notation discussion: asymptotic claims hold for large values of n. For small values of , can be larger than — the ordering of the functions flips in the small-n regime. The instructor's warning: this is one of those small things you will miss if you apply the asymptotic intuition mechanically. The solution document itself reads "2n by 4" in shorthand — do not confuse that with ; the intended function is , which for gives 32.
7.21.2 Open Addressing with Three-Slot Buckets
The next question: insert data into a hash table implemented using the open addressing technique. The question states explicitly: apply linear probing for collision resolution, gives the hash function, and says to assume the buckets have 3 slots each. The instructor's note: there is nothing tricky — you can do it easily; only there will be some calculations. The "3 slots each" variant just means each bucket cell can hold up to three entries before probing moves on; the linear-probing mechanics from Section 7.5 apply.
7.21.3 Reconstructing a Tree from Two Traversals
The paper includes: find the pre-order traversal of a binary tree given the in-order and post-order traversals. The instructor's commitment on this topic: one question from "construct the tree given any two traversals" is compulsory — that question is already worth 3 marks, and nobody should lose marks on it. Sometimes the variant asks to construct a full binary tree given post-order and pre-order alone, without in-order. The method was covered in detail in the previous session: given two traversals, find the tree and then find the third traversal — follow that method exactly, or you will not be able to solve it.
7.21.4 Max Heap Insertion and Counting Swaps
The paper asks: consider the following array of a max heap; insert 95 into the array. Questions: how many swaps are required? (explain the swaps). And: give the final array after insertion.
The instructor's advice: first construct the max heap — you need to construct it (or, if you are very confident, you can work in the array directly; showing it in the array is the best thing). The key warning: read the question carefully — it asks for the final array after insertion. Students routinely hand in just the final heap, and that costs marks (two marks were mentioned as cut). Deliver exactly what is asked: the final array.
Illustrative trace of the mechanics. The paper's exact array was not read out in the session, but the procedure is fixed, so the mechanics can be practiced on any heap. Take the max heap stored as the array
(level order: root 80; children 60 and 40; grandchildren 30, 20, 10, 5 — every parent is at least as large as its children, so it is a valid max heap).
Step 1 — Place 95 at the end of the array. The heap grows at the last position: . The parent of index 7 is index 3 (value 30).
Step 2 — Bubble up. While the new element is larger than its parent, swap them and move up:
- 95 > 30 (parent) → swap: . 1 swap. Now at index 3; parent is index 1 (value 60).
- 95 > 60 → swap: . 2 swaps. Now at index 1; parent is index 0 (value 80).
- 95 > 80 → swap: . 3 swaps. Now at index 0 — the root.
Answer: 3 swaps required; final array . Sense-check. The root is 95 (the largest), every parent exceeds its children (80 > 60, 20; 40 > 10, 5), and the heap property holds — each swap was forced by "child larger than parent", so the count of 3 is exactly the number of violations fixed along the path.
7.21.5 The Circular Queue Size Formula
A queue is set up in a circular array with front and rear defined as usual, and locations of the array are available for storing elements. Give a formula for the number of elements in the queue in terms of rear, front, and . The answer, discussed fully in class:
where is the front index, is the rear index, and is the array capacity. The instructor noted this was asked in a closed-book offline paper; in the online format, questions are posed as scenarios so that a direct web search does not hand over the solution.
Worked example — why the formula is right. In a circular array of cells (indices 0 to 4), suppose the front is at and the rear is at . The convention: front points at the oldest element, rear points at the next free slot, and one cell is kept spare to tell "empty" from "full" (so usable locations).
The formula:
2 elements are in the queue — sitting at cells 2 and 3 (front and the cell before rear). Sense-check. Empty queue: , so — correct. Full queue in : , (one cell spare): — correct, elements at capacity.
7.21.6 Array-Based Queue Resizing
An array-based queue throws an exception when the array's capacity has been reached. Suppose we use a resize method to expand the array each time it is full; the cost of a resize that makes the array larger is proportional to the new size. Two cases are asked about: (a) we expand the array's capacity by one element each time — analyze the running time; (b) we double the array's capacity each time — analyze the time complexity.
This was discussed completely in class. Expanding by one costs per resize, and doing that for insertions sums to about total work, i.e. amortized per insertion. Doubling the capacity makes the total work (each element is copied at most logarithmically many times), i.e. amortized per insertion.
The amortized sums, worked out. Resizing copies every existing element into the new array, so a resize to size costs .
- Expand by one: resizes happen after every insertion, at sizes 1, 2, 3, ..., . Total copying work:
So insertions cost total — and per insertion on average (amortized), which is no better than a plain list.
- Double the capacity: resizes happen only when the array is full — at sizes 1, 2, 4, 8, ..., up to . Total copying work:
(a geometric series: each copy work is at most double the previous, and the sum of a doubling series is less than twice its largest term). So insertions cost total — amortized per insertion. Every element is copied only when its array doubles: at most times per element, and total would be an overcount — the geometric sum shows the true total is .
This is the standard dynamic-array analysis; the same argument applies to the hash table's doubling-and-rehash growth from Section 7.3 — doubling makes growth cheap on average, growing by one makes it expensive.
7.21.7 The Stock Span Problem
A scenario-based question: oil marketing companies have decided to perform an analysis on crude oil prices for number of days. The aim is to find, for every day, the number of days in a row preceding the present day when the price of the crude oil was not greater than the price on the present day. Describe a linear-time algorithm to solve this using a stack.
This is the stock span problem from the stack material, present in the course slides. The instructor's note: there is a very simple catch — it is just manipulation of indices in a stack; the solution is very small. The difficulty is understanding the question, not writing the solution.
Algorithm — linear-time stock span.
- Input: prices for days.
- Output: span = number of consecutive days ending at day whose price was not greater than .
- Idea: keep a stack of day indices whose prices are strictly decreasing from bottom to top. While the stack top's price is not greater than the current price, pop it (those days cannot bound the current span). The current span is the distance from the current day to the new top:
- Cost: each index is pushed once and popped once — total, linear time.
A linear-time stack algorithm processes the prices left to right, popping indices whose prices are no greater than the current price, so the current day's span is the distance to the new top of the stack.
Trace — prices [100, 80, 60, 70, 60, 75, 85].
| Day | Price | Pop while ≤ price | New top | Span |
|---|---|---|---|---|
| 0 | 100 | — | (empty) | 0 + 1 = 1 |
| 1 | 80 | none (100 > 80) | 0 | 1 − 0 = 1 |
| 2 | 60 | none (80 > 60) | 1 | 2 − 1 = 1 |
| 3 | 70 | pop 2 (60 ≤ 70) | 1 | 3 − 1 = 2 |
| 4 | 60 | none (70 > 60) | 3 | 4 − 3 = 1 |
| 5 | 75 | pop 4, pop 3 (60, 70 ≤ 75) | 1 | 5 − 1 = 4 |
| 6 | 85 | pop 5, pop 1 (75, 80 ≤ 85) | (empty) | 6 + 1 = 7 |
Spans: [1, 1, 1, 2, 1, 4, 7]. Sense-check. Day 6's price 85 beats all six earlier prices, so its span is all 7 days; day 5's 75 beats days 2–5 (60, 70, 60) but not day 1's 80, so its span is 4 — both match the definition exactly.
7.21.8 Common Pitfalls
- Applying asymptotic intuition at small . At , beats ; choosing by "polynomial beats exponential" loses the marks. Compute the given numbers first, then decide.
- Handing in the heap instead of the final array. The heap question asks for the final array after insertion — two marks were mentioned as cut for giving only the heap (or only the swaps). Deliver exactly what the question asks.
- Misreading the stock-span scenario. The question is wrapped in crude-oil wording; underneath it is index manipulation with a stack. The difficulty is decoding the scenario, not the algorithm — write the spans left to right and pop while the top's price is no greater.
- Mixing up the queue formula. counts from front (inclusive) to rear (exclusive); forgetting the mod (or the trick to keep the difference positive) gives negative or out-of-range counts.
7.21.9 Student Questions and Answers
Q: Is the complexity of the regular and makeup exams the same? A: If you have been given the option to choose some papers for regular and some for makeup, then yes — it will be the same complexity. If the makeup paper is only for emergencies (when you could not take the regular one), then the complexity may be different.
Q: Are master's theorem and the substitution method in scope? Where are they? A: They are there — in the handout. The master's theorem and substitution method material will be there; the instructor just did not mention it during this walkthrough. Also expect one heap question, hashing questions, one stack or queue question, and one algorithm-writing question in the final paper.
Exam note: the paper shown was one of the simpler ones — an offline paper where "all of you would score at least 25" because the questions were direct. The current format has not made the content more complex; it changed the way of asking. Instead of directly asking you to do something, the exam gives a scenario and asks you to solve it. The crude-oil question is the model: direct solution, but the scenario has to be decoded first.
Recap + Bridge: The paper re-tested everything from this session and earlier ones — small-n algorithm choice, open-addressing insertion, tree reconstruction, heap insertion with swap counting, the circular queue formula, queue resizing, and the stock-span stack algorithm. These are the shapes to expect; the closing summary collects the exam guidance into one place.
Real-world connection: every question in this walkthrough maps to a real system — resizing rules are how dynamic arrays and hash tables grow in production, the stock-span stack is the same monotonic-stack technique used in financial analytics for rolling highs and lows, and heap insertion with swap counting is the core of every priority queue in schedulers and event loops.
Exam Guidance Summary
Exam shape (stated by the instructor): there will be one heap question, hashing questions (a hash question was called "100% sure" — expect an open-addressing insertion), definitely one stack or queue question, and one question asking you to write an algorithm. One question from "construct the tree given any two traversals" is compulsory — worth about 3 marks — and the instructor said nobody should lose marks on it. The final paper is fixed by a syllabus committee: the instructor submits questions, but the paper must cover the assigned topics and cannot be biased; if a paper omits topics the committee rejects it. Papers also get tweaked per company collaboration for other cohorts, so the exact phrasing of a past paper may differ from yours — don't argue "it was asked this way" later; the concepts are what matter.
Question types and patterns:
- Expect a hashing question using open addressing with a specified collision method (e.g., linear probing), a given hash function, and possibly a twist like buckets with 3 slots each. If a probing formula is not specified for quadratic probing, either or is acceptable — but be consistent.
- For double hashing, both hash functions are given in the question; if the second one is not given, use with prime.
- The kth-smallest BST question has appeared in an earlier paper. The question will state whether to trace the algorithm or give the algorithm — deliver exactly that.
- Expect a "which BST is formed by this insertion order" identification question — the trap is checking only the immediate parent instead of all ancestors.
- LCA questions have solutions in the material — work through them; skipping them means failing similar exam questions.
- A heap question (insert a value, count the swaps, give the final array) — construct the heap, read the question carefully, and give exactly what is asked (e.g., the final array, not just the heap).
- The circular queue size formula was a past closed-book question — memorize the mechanics, because in the online format the same idea arrives wrapped in a scenario.
- The queue-resize amortized analysis (expand by one vs double) is examinable and was discussed in full in class.
- The stock span problem (crude oil prices, linear-time stack algorithm) is in the slides — a model for how scenario questions work.
Algorithm-writing rules: if asked to write an algorithm, the required complexity will be stated — "write a quadratic time algorithm" or "write a linear time algorithm". If the question demands linear time and you submit a quadratic algorithm, the answer is cancelled and credited zero. The same applies to choosing between algorithms: compute for the given (small) rather than assuming the asymptotically better function wins.
Content and difficulty notes:
- There is no coding on the exam — no code at all; it is concept-based.
- Time complexity may not be included in the mid-sem paper — the instructor was unsure ("I think I'm not sure"), so do not bet the paper on it, and know the material anyway.
- The master's theorem and the substitution method are in the handout and are in scope — the instructor confirmed they will be there even though they were not discussed during the paper walkthrough.
- Past papers: all the exercise questions in the course material are past exam questions — by now solving all of them means you have effectively solved at least three papers. Try each one yourself first, then check the solution. "Be true to yourself."
- Study references: recorded lectures, the T1, T2, R1, R2 references, and Cormen (CLRS) if you want more depth. Going through the PPTs alone will not be enough.
- Use the array representation of a tree in answers unless the question explicitly asks for the tree representation.
- The load factor default is 0.75 when not given. Rehashing: double the size, rehash all existing elements.
- Regular vs makeup: same complexity when you are given the choice; different when makeup is emergency-only.
- Reading carefully: the heap question is the warning case — most lost marks come from answering a different question than the one asked (final array, not final heap).
- Expected number of probes for open addressing: ; worst case for all hashing: . Both worth knowing with the load factor below 1 (preferably 0.75).
Study advice (the instructor's parting words): this was the last class before the exam; the exercises in the material are the best preparation — solve them, then compare with the solutions. The recorded lectures, the reference textbooks, and the exercises together are enough; PPTs alone are not.
Key Industry Applications
Real-world: cloud storage provisioning (Dropbox) — storage is allotted incrementally, roughly half of what you request at first; when usage reaches about 75% of the allotment, a notification fires and the provider doubles the capacity. This is exactly the hash-table rehashing pattern: grow by doubling at the load factor, rather than paying the full cost up front. The instructor used it to justify why rehashing cost is acceptable compared with allotting complete storage initially.
Real-world: hash function design for strings — real-world hash functions for string keys use polynomial hashing (evaluate the characters as a polynomial), and alternatives like hexadecimal encoding and polynomial encoding were named; the instructor pointed to a web search on Google for "polynomial hashing" as a follow-up. The engineering lesson: pick a hash function that reduces operational complexity for your key type — the lecture's mod-by-length function was only a teaching toy.
Real-world: choosing hash tables vs alternatives in applications — the decision depends on the application: if you can predict the number of incoming elements, a simple hash function with large initial capacity avoids rehashing; otherwise you pay for good hash functions and growth. If a worst case (all keys colliding) is guaranteed by the data, the professional answer is to not use a hash table.
Real-world: crude oil price analysis (stock span) — oil marketing companies analyze daily crude oil prices to find, for each day, how many consecutive preceding days had prices no greater than the current price; a linear-time stack algorithm solves it. This is the model scenario-based exam question and a real analytics task.
Real-world: dictionary implementations — every language's dictionary/associative-array implementation (the session's running example) is a hash table with separate chaining or open addressing, a load-factor-triggered doubling resize, and rehashing on growth.
Real-world: binary search trees — BSTs underpin ordered maps and ordered sets in standard libraries; the search, insert, delete, successor/predecessor, and kth-smallest operations shown here are the exact operations such structures expose, and the skewing warning (sorted insertions → ) is the reason balanced variants exist in production.
DSA Lecture 7 notes · Hashing and Binary Search Trees
Sections Breakdown
A good hash function spreads keys evenly so most buckets hold zero or one entry, keeping retrieval O(1); the load factor alpha = n/N bounds how full the table may get (default 0.75) before capacity doubles.
Dictionary operations on a hash table run in Theta(n/N) = Theta(alpha) expected time, which collapses to O(1) only while the number of entries stays within the capacity (n = O(N)), enforced by load-factor-triggered resizing.
The compression map brings hash codes into the range [0, N-1] and depends on capacity N; growing the bucket array changes the modulus, so every existing element must be re-inserted under the new map — a step called rehashing, triggered when the load factor crosses its bound.
Open addressing places colliding items in other cells of the same bucket array, saving the memory of chaining at the cost of probe arithmetic; its three methods are linear probing, quadratic probing, and double hashing.
Linear probing places a colliding item in the next circularly available cell, A[(i+j) mod N]; search replays the same circular sweep and stops at a match or a genuinely empty cell, which makes deletion require an available marker.
Deleting from an open-addressed table replaces the removed element with a special available marker: searches pass over available cells but stop at truly empty cells, while inserts may reuse available cells.
Primary clustering is the tendency of linear probing to stack colliding keys into a contiguous block around their original hash locations; outsiders whose hash falls inside the block must probe across it, so the penalty bleeds over to unrelated keys.
Quadratic probing resolves collisions by probing at squared offsets, (H(K)+i^2) mod N or (H(K)+i+i^2) mod N; either form is acceptable but one must be used consistently for all elements; the worked example places 76,40,48,5,55 in a 7-cell table.
Secondary clustering is the group-local failure of quadratic probing: all keys with the same initial hash share the same probe sequence, so the group collides with itself; a prime capacity N makes the probe cycle visit more distinct cells and mitigates the failure.
Double hashing uses a second hash function H'(K) = Q - (K mod Q), Q prime, to give each key its own probe step; the probe sequence is (H(K) + i*H'(K)) mod N with i = 1,2,3,... and only i changes on further collisions.
The worst case for search/insert/remove in a hash table is O(n) when all keys collide, while the expected number of probes for insertion with open addressing is 1/(1-alpha) under uniform hashing — the mathematical reason the load factor must stay below 1 (preferably 0.75).
A binary search tree is a binary tree where every key in a node's left subtree is smaller than the node's key and every key in its right subtree is larger (or equal), making the inorder traversal output the keys in sorted order; external nodes store no items.
BST search follows a single downward path from the root, comparing the key at each node and discarding one whole subtree per step, in O(h) time — about log2 n for a balanced tree.
Insertion into a BST is a failed search: follow the key's downward path, then plant the new key at the terminal external node, expanding it into an internal node, in O(h) time with no rearrangement.
The inorder successor is the smallest key larger than a given key (leftmost of the right subtree, or first ancestor climbed until a left child); the predecessor is its mirror image (rightmost of the left subtree, or first ancestor climbed until a right child).
Deleting a BST node has three cases: no children (remove it), one child (splice the child up), two children (replace the value with the inorder successor or predecessor, which has at most one child, then delete that neighbor).
The BST property guarantees ordering but not balance: a balanced tree gives O(log n) per operation, while sorted insertions build a skewed tree of height n where every operation degrades to O(n).
The kth-smallest algorithm walks the tree top-down comparing the target rank k with the left-subtree count: return when k = left+1, subtract and go right when k > left, go left otherwise; the trace finds the 4th smallest as 8.
Inserting 55, 63, 31, 17, 22, 40 in that order builds a unique BST (55 root, 63 right, 31 left, 17 left of 31, 22 right of 17, 40 right of 31); the common exam trap is checking only the immediate parent instead of all ancestors.
The LCA of two keys in a BST is found by one downward walk: descend left when both are smaller, right when both are larger, and stop at the first node where they split or one equals it; a node counts as its own ancestor.
A past paper walkthrough: for n <= 7 algorithm two (2^n/4 = 32) beats n^2 (49); open addressing with 3-slot buckets; tree reconstruction from two traversals; max heap insertion with swap counting; circular queue formula (n-f+r) mod n; queue resizing amortization; and the stock span problem solved in linear time with a stack.
Exam shape: one heap question, hashing questions (open addressing expected), one stack or queue question, one algorithm-writing question, and a compulsory 3-mark tree-reconstruction question; no coding; scenarios replace direct questions in the online format.
Named applications: Dropbox-style incremental storage allotment (rehashing), polynomial hashing for string keys, hash-table vs alternative selection, crude-oil stock span analytics, dictionary implementations, and BSTs behind ordered maps and sets.
Exam Revision Notes
Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.
Good Hash Functions and the Load Factor
Must-know: Load factor alpha = n/N; if not given, assume 0.75; capacity 16 with 0.75 allows 12 elements before doubling.
⚠️ Top pitfall: Treating the load factor as a count instead of a ratio; forgetting that doubling the capacity requires rehashing all existing elements.
Self-check: Capacity 100, load factor 0.75: how many elements before the table grows? (75)
Connects to: Expected Running Time of Dictionary Operations, The Compression Map and Rehashing
Expected Running Time of Dictionary Operations
Must-know: Expected dictionary time is Theta(n/N) = Theta(alpha); it is O(1) only under the assumption n is bounded by N. Every bucket holding one entry means retrieval is O(1).
⚠️ Top pitfall: Saying dictionary operations are O(1) blindly, without the bounded-load-factor assumption; answering O(n) for single-entry buckets.
Self-check: If every bucket holds exactly one entry, what is the retrieval time? (O(1))
Connects to: Good Hash Functions and the Load Factor, The Compression Map and Rehashing, Complexity of Hashing — Worst Case and Expected Probes
The Compression Map and Rehashing
Must-know: Rehash = double the array size AND re-insert every existing element under the new compression map; valid indices run 0 to N-1; load factor default 0.75 triggers the grow.
⚠️ Top pitfall: Doubling the array without rehashing, or believing rehashing decreases time complexity.
Self-check: N = 6 with 5 elements at load factor 0.75: can the 5th element be inserted before rehashing? (No, 5/6 > 0.75)
Connects to: Good Hash Functions and the Load Factor, Expected Running Time of Dictionary Operations, Open Addressing — an Overview
Open Addressing — an Overview
Must-know: Open addressing stores every item in the bucket array itself; three methods: linear probing, quadratic probing, double hashing; load factor must stay below 1.
⚠️ Top pitfall: Thinking 'open' means the table is publicly accessible; it means the table is open to colliding items.
Self-check: What is the trade-off between separate chaining and open addressing? (space saved vs probe complexity)
Connects to: The Compression Map and Rehashing, Linear Probing, Quadratic Probing, Double Hashing
Linear Probing
Must-know: Probe sequence A[(i+j) mod N], j = 0,1,2,...; search 32 probes 6 -> 7 -> 8; search stops at empty cell because insertion places keys in the first free cell at or after the hash position.
⚠️ Top pitfall: Stopping a search at the first occupied cell; forgetting the mod-N wrap; treating a deleted-but-unmarked cell as empty.
Self-check: After inserting 18,41,22,44,59,32 into an empty table with H(K) = K mod 13, where is 32? (cell 8)
Connects to: Deletion in Open Addressing, Primary Clustering, Complexity of Hashing — Worst Case and Expected Probes
Deletion in Open Addressing
Must-know: Delete by replacing the item with the available marker, not by clearing the cell; searches continue past available cells and stop only at truly empty cells; worst case for all hashing is O(n).
⚠️ Top pitfall: Clearing a deleted cell to empty breaks probe sequences; searching must not stop at an available cell.
Self-check: After deleting 44 from the table of Section 7.5.2, why does the search for 32 still succeed? (cell 6 is marked available, so the sweep passes through)
Connects to: Linear Probing, Complexity of Hashing — Worst Case and Expected Probes
Primary Clustering
Must-know: Multiples of 17 all hash to 0 and fill cells 0..6 consecutively; outsider 20 (hash 3) must probe 3,4,5,6 before landing at cell 7; primary clustering hurts unrelated keys.
⚠️ Top pitfall: Thinking only the colliding group suffers; the outsider 20 shows the cluster penalizes unrelated keys too.
Self-check: With keys 0,17,34,51,68,85,102 in a 17-cell table, where does 20 land? (cell 7)
Connects to: Linear Probing, Quadratic Probing, Secondary Clustering
Quadratic Probing
Must-know: Use one formula consistently: (H(K)+i^2) mod N or (H(K)+i+i^2) mod N with i = 1,2,3,...; 55 probes 6 -> 1 -> 5 -> 4 (cell 4); never combine linear and quadratic probing.
⚠️ Top pitfall: Mixing probe formulas mid-solution or combining linear and quadratic probing; forgetting the mod on the probe result.
Self-check: With N = 7, H(K) = K mod 7, quadratic probing (H(K)+i+i^2): where does 55 land? (cell 4)
Connects to: Linear Probing, Secondary Clustering, Double Hashing
Secondary Clustering
Must-know: Same initial hash => same probe sequence (0,2,6,12,3,13,8 for multiples of 17); outsiders do not suffer; prime N visits more distinct cells; quadratic probing may fail to find an empty slot even when the array is not full.
⚠️ Top pitfall: Thinking secondary clustering hurts outsiders too; using a composite N; assuming quadratic probing always finds an empty cell.
Self-check: For the multiples of 17 with N = 17, what probe sequence does each member walk? (0, 2, 6, 12, 3, 13, 8, ...)
Connects to: Primary Clustering, Quadratic Probing, Double Hashing
Double Hashing
Must-know: On collision: H'(44) = 7 - (44 mod 7) = 5, probe (5 + 1*5) mod 13 = 10; second hash used only on collision; only i advances afterwards; default H' = Q - (K mod Q), Q prime.
⚠️ Top pitfall: Computing the second hash without a collision; inventing a third hash function when the first probe collides.
Self-check: With N = 13, H(K) = K mod 13, H'(K) = 7 - (K mod 7), where does 44 go? (cell 10)
Connects to: Quadratic Probing, Secondary Clustering, Complexity of Hashing — Worst Case and Expected Probes
Complexity of Hashing — Worst Case and Expected Probes
Must-know: Worst case O(n) when all keys collide; expected probes for insertion = 1/(1-alpha), which is 4 at alpha 0.75 and blows up as alpha -> 1; successful search companion form (1/alpha) ln(1/(1-alpha)).
⚠️ Top pitfall: Quoting O(1) without the bounded-load-factor assumption; using the formula at alpha >= 1 where it is undefined.
Self-check: At load factor 0.75, what is the expected number of probes for an insertion? (4)
Connects to: Expected Running Time of Dictionary Operations, Linear Probing, Double Hashing
Binary Search Trees — Definition and Property
Must-know: key(u) < key(v) <= key(w) for u in left subtree, w in right subtree; the property holds against all ancestors; inorder traversal of the session tree gives 1,2,4,6,8,9; external nodes store nothing.
⚠️ Top pitfall: Checking the property only against the immediate parent instead of all ancestors; treating external empty nodes as data.
Self-check: Why does inorder traversal of a BST output sorted keys? (left subtree < root <= right subtree at every node)
Connects to: Searching in a BST, BST Performance — Balanced Trees and Skewed Trees, Past Exam Question — Which BST Does an Insertion Order Produce?
Searching in a BST
Must-know: Search is O(h); for a balanced binary tree h is about log2 n; finding 4 in the session tree takes the path 6 -> 2 -> 4 (two comparisons); one subtree is discarded at every step.
⚠️ Top pitfall: Quoting O(log n) without the balanced-tree assumption; forgetting the external-node exit for a missing key.
Self-check: In the tree root 6 (left 2 with 1,4; right 9 with 8), what path does searching for 4 take? (6 -> 2 -> 4)
Connects to: Binary Search Trees — Definition and Property, Inserting into a BST, BST Performance — Balanced Trees and Skewed Trees
Inserting into a BST
Must-know: Insert 5: 5 < 6, 5 > 2, 5 > 4 -> right child of 4; insert 10: right child of 9; insertion is always at a leaf where the search ends, never between nodes.
⚠️ Top pitfall: Trying to insert between nodes; rearranging the tree on insertion; forgetting insertion cost is O(h), not O(log n) in a skewed tree.
Self-check: Where does 5 go when inserted into the tree root 6 (left 2 with 1,4; right 9 with 8)? (right child of 4)
Connects to: Searching in a BST, Inorder Successor and Predecessor, Deleting from a BST, BST Performance — Balanced Trees and Skewed Trees
Inorder Successor and Predecessor
Must-know: Predecessor of 20 = rightmost of left subtree = 14; predecessor of 12 = 10 (12 is right child of 10); predecessor of 8 does not exist; successor climbs until a left child, predecessor until a right child.
⚠️ Top pitfall: Swapping the climb directions (successor climbs until left child, predecessor until right child); stopping the climb at the first ancestor regardless of child direction.
Self-check: In Tree 2 (root 20, left 8-12-14, right 30 with 25), what is the successor of 14? (20)
Connects to: Deleting from a BST, Binary Search Trees — Definition and Property
Deleting from a BST
Must-know: Delete 20 with two children: replace with predecessor 19 (or successor 30); delete 3: replace with successor 5; the replacement node has at most one child; all operations stay O(h), O(log n) when balanced.
⚠️ Top pitfall: Removing the two-child node itself instead of replacing its value; worrying that the replacement node has children (it has at most one).
Self-check: When deleting a node with two children, why must the replacement be the inorder successor or predecessor? (only they can keep both subtrees ordered)
Connects to: Inorder Successor and Predecessor, BST Performance — Balanced Trees and Skewed Trees
BST Performance — Balanced Trees and Skewed Trees
Must-know: Inserting 1,2,3,4,5 in order makes a right-skewed tree of height 5; all operations become O(n); balanced BST keeps everything O(log n); the fix belongs to balancing material.
⚠️ Top pitfall: Quoting O(log n) without the balanced-tree qualification; believing the BST property forces balance.
Self-check: What happens to the height if keys are inserted in sorted order? (height = n, all operations O(n))
Connects to: Binary Search Trees — Definition and Property, Searching in a BST, The Kth Smallest Element in a BST
The Kth Smallest Element in a BST
Must-know: Trace for k = 4: at 20 (left 7) go left; at 10 (left 4) go left; at 6 (left 1) k = 4 - 2 = 2 go right; at 8 (left 1): k = 2 = left + 1 -> answer 8. This question appeared in an earlier paper.
⚠️ Top pitfall: Confusing 0-based rank with 1-based k; forgetting to subtract left+1 when moving right; answering with inorder traversal when the question asks to trace the algorithm.
Self-check: In the session tree, what is the 4th smallest element? (8)
Connects to: Binary Search Trees — Definition and Property, Inorder Successor and Predecessor
Past Exam Question — Which BST Does an Insertion Order Produce?
Must-know: Correct tree: 55 root, 63 right, 31 left, 17 left of 31, 22 right of 17, 40 right of 31; the distractor puts 31 and 40 in 55's right subtree; always check all ancestors, not just the parent.
⚠️ Top pitfall: Checking only the immediate parent instead of all ancestors; most of the class chose the wrong option on this exact trap.
Self-check: In the insertion order 55, 63, 31, 17, 22, 40, where does 40 go? (right child of 31, still in 55's left subtree)
Connects to: Binary Search Trees — Definition and Property, Inserting into a BST, The Least Common Ancestor in a BST
The Least Common Ancestor in a BST
Must-know: LCA(10,14) = 12 (split at 12); LCA(14,8) = 8 (8 is its own ancestor); LCA(10,22) = 20 (split at root); stop at the first node where the values split or one equals it.
⚠️ Top pitfall: Reporting a shallower common ancestor (20 instead of 8); forgetting that a node is its own ancestor.
Self-check: In the tree (20 left 8 right 22; 8 right 12 with children 10, 14), what is LCA(10, 22)? (20)
Connects to: Binary Search Trees — Definition and Property, Past Exam Question — Which BST Does an Insertion Order Produce?
Past Paper Walkthrough
Must-know: At n = 7 choose algorithm two (32 < 49); circular queue size is (n-f+r) mod n; queue resizing: expand by one gives O(n^2) total / O(n) amortized, doubling gives O(n) total / O(1) amortized; stock span is linear-time index manipulation with a stack.
⚠️ Top pitfall: Applying asymptotic intuition at small n; handing in the final heap instead of the final array; misreading the stock-span scenario.
Self-check: For n insertions, why does doubling the queue capacity give O(1) amortized insertion? (geometric sum 1+2+4+...+2^floor(log2 n) < 2n)
Connects to: The Compression Map and Rehashing, Linear Probing, Complexity of Hashing — Worst Case and Expected Probes
Exam Guidance Summary
Must-know: No coding; load factor default 0.75; rehash = double size and reinsert all elements; circular queue formula (n-f+r) mod n; expected probes 1/(1-alpha); worst case O(n); master's theorem and substitution method are in the handout.
⚠️ Top pitfall: Answering a different question than the one asked (e.g., final heap instead of final array); submitting a quadratic algorithm when linear time was demanded.
Self-check: What does the instructor say to expect in the paper? (one heap question, hashing questions, one stack/queue question, one algorithm-writing question)
Connects to: Good Hash Functions and the Load Factor, Complexity of Hashing — Worst Case and Expected Probes, The Kth Smallest Element in a BST, Past Paper Walkthrough
Key Industry Applications
Must-know: Rehashing mirrors cloud storage allotment (double at ~75% usage); string hashing uses polynomial hashing; if all keys can collide, do not use a hash table.
⚠️ Top pitfall: Using the lecture's mod-by-length hash as a production design — it is a teaching toy, not an engineering choice.
Self-check: Why is incremental doubling of cloud storage acceptable compared with allotting full capacity at once? (rehashing cost is small relative to up-front allocation)
Connects to: The Compression Map and Rehashing, BST Performance — Balanced Trees and Skewed Trees
Was this lecture useful?
BitsNotes AI Assistant
Subject Notes AssistantConfigure AI Chat
Choose how to access the chatbotSigned in as
Powered by BitsNotes — 20 messages per day. No API key needed. Want unlimited access? Use "Bring Your Own Key" mode.
Sign in to use AI Chat
Get 20 free AI messages per day to ask questions about your lecture notes. Sign in with Google or GitHub — it takes 5 seconds.
Sign In to BitsNotesSwitch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.