ITC 120 › Final › Hashing
Hashing · Lesson 1 of 1
Hashing and Hash Tables
This lesson teaches:TechniqueData structureTurn a key into a slot number, so a search can jump straight to it. Then learn what to do when two keys want the same slot.
By the end, you can:
- explain what hashing, a hash function, a hash table, and a collision are;
- place keys into a table by hand with h(k) = k mod m, using separate chaining, linear probing, quadratic probing, and double hashing;
- search a hash table, and explain why a deleted slot gets a mark instead of being emptied;
- work out the load factor, and explain when and how a table is rehashed;
- read and complete a hash table in Java;
- compare a hash table's average and worst-case speed with linear search, binary search, and a BST, and choose when to use it.
You need: the Arrays and Linked lists lessons, the Searching lesson, the Maps lesson (your first look at hashing), and scratch paper.
Part 1 · The idea
Jump straight to the right place
You already know two ways to find a value: linear search and binary search . Linear search may check every value. Binary search is much faster, but the values must be sorted, and it still needs about 20 checks for a million values.
What if one small calculation told you exactly where a value is? Then you would look in one place only. Offices do this with paper every day:
Think of the registrar's trays
During enrollment, the registrar puts forms into 10 trays, numbered 0 to 9. The rule: use the last digit of the student ID. Form 2025-0347 goes into tray 7. Later, to find that form, the staff do not search all the trays. They use the same rule and look in tray 7 only.
Hashing
Hashing means using a fixed rule to turn a key into a slot number, and storing the key in that slot. To find the key later, use the same rule again and look in that one slot.
You met this idea in the Maps lesson, where Java's HashMap did the hashing for you (see how a HashMap finds a bucket ). In this lesson, you build the table yourself, with simple whole-number keys such as ID numbers.
Hashing comes with a few words. The table below matches each one to the trays:
| Word | Meaning | With the trays |
|---|---|---|
| key | the value we store and later look for | the student ID |
| hash table | an array of slots where the keys are kept | the row of trays |
| m | the table size: how many slots there are | 10 |
| slot (or bucket) | one place in the table, numbered 0 to m − 1. "Bucket" is used when a slot can hold more than one key. | one tray |
| hash function, h(k) | the rule that turns a key k into a slot number | "take the last digit" |
| collision | two different keys get the same slot number | 2025-0347 and 2025-0127 both go to tray 7 |
Where you see hashing in IT
- Java's
HashMapandHashSet(Sets and Maps lessons) are hash tables inside. - Databases can build a hash index, so finding a record by its ID does not mean reading the whole table.
- A compiler keeps the names of your variables in a hash table, so it can look each one up quickly.
Good to know: you may also hear about "password hashing". It uses a special kind of hash function (a cryptographic one) that is built so nobody can work backward from the number to the password. Same idea of a fixed rule, different goal, and not part of this course.
Part 2 · The rule
Hash functions: k mod m
Hash function
A hash function is a fixed rule that takes a key and gives back a slot number from 0 to m − 1. We write it h(k): "h of k". The same key must always give the same number, or you could never find it again.
The most common rule for whole-number keys is the remainder after dividing by m. Mathematicians call the remainder "mod", and Java writes it % :
h(k) = k mod m (in Java: key % m)
Here is how to work it out by hand: divide, and keep only what is left over.
| k mod m | Working | Answer |
|---|---|---|
| 37 mod 5 | 37 = 7 × 5 + 2 | 2 |
| 50 mod 7 | 50 = 7 × 7 + 1 | 1 |
| 700 mod 7 | 700 = 100 × 7 + 0 | 0 |
| 4 mod 7 | 4 = 0 × 7 + 4 (smaller than m: the key itself) | 4 |
A remainder after dividing by m is always 0, 1, …, m − 1: exactly the slot numbers. That is why this rule fits a table of m slots so well. (The trays used it too: the last digit of a number is the number mod 10.)
Course convention
In this course, keys are whole numbers (0 or more) and the hash function is h(k) = k mod m, unless a question gives another one. Books call it the division method.
What makes a good hash function?
A good hash function has four properties:
- Same key, same slot, every time. Otherwise a stored key could never be found again.
- Quick to compute. The whole point is speed.
- Spreads the keys evenly over all the slots, so few keys collide.
- Uses the whole key, not just a part of it.
Property 4 is where the table size matters. A sari-sari store keeps its prices in a hash table with m = 10. Prices are often round numbers: ₱10, ₱20, ₱30, ₱40, ₱50. But k mod 10 is just the last digit, and every one of these ends in 0:
With m = 10, all five prices pile into slot 0, and finding one is as slow as searching a list. With a prime table size such as 7, 11, or 101, the remainder depends on the whole number, so keys that share a pattern still spread out. That is why books (and exams) like prime table sizes.
Good to know: Java's HashMap uses table sizes 16, 32, 64, … and finds the bucket with hashCode & (capacity − 1), as the Maps lesson showed. For sizes that are powers of 2, that gives the same answer as mod, only faster. To avoid the "only the last digits matter" problem, Java first mixes the hash code's high bits into its low bits.
Quick check: with m = 11, which slots do 25, 36, and 47 get?
25 = 2 × 11 + 3, 36 = 3 × 11 + 3, 47 = 4 × 11 + 3. All three get slot 3! Even a good rule cannot stop that. The next part explains what to do.
Part 3 · When keys clash
Collisions
Collision
A collision happens when two different keys get the same slot number: h(k1) = h(k2). Both keys must still be stored, and both must be found later.
Can a clever hash function avoid collisions completely? No. Student IDs can go from 0 to 99,999,999, but the registrar has only 10 trays. With more keys than slots, some keys must share. (This is the pigeonhole principle: put 11 letters into 10 pigeonholes, and at least one pigeonhole gets two.) So every hash table needs a plan for collisions.
There are two families of plans:
| Family | The idea | In real life |
|---|---|---|
| Separate chaining (Part 4) | Each slot keeps a linked list (a chain) of all the keys that hash to it. | Two forms in tray 7: one on top of the other. |
| Open addressing (Parts 5 and 6) | Each slot holds one key. If the slot is taken, try other slots in a fixed order until one is free. | Your parking slot is taken, so you try the next one. |
To compare the methods fairly, every example in Parts 4 to 6 uses the same six keys and the same table size:
keys: 50, 700, 76, 85, 92, 73 · m = 7 · h(k) = k mod 7
Part 4 · Method 1
Separate chaining
Separate chaining
In separate chaining, every slot (called a bucket) holds a linked list , called its chain, of all the keys that hash to it.
Insert k: compute h(k) and add k to that bucket's chain.
Search for k: compute h(k) and walk that one chain.
Course convention
A new key goes at the end of its chain, as in Java's HashMap. Some books add it at the front instead (no walk needed). Both are correct; follow the question if it says which.
Step through the six keys:
Separate chaining, by hand (m = 7)
Use Next to go through the steps.
An empty table with 7 buckets (0 to 6). Every bucket starts as null: no chain yet. We insert the keys in order: 50, 700, 76, 85, 92, 73.
On paper (and on exams), write each bucket with its chain:
0: 700
1: 50 → 85 → 92
2:
3: 73
4:
5:
6: 76 Searching follows the same rule. To find 92: 92 mod 7 = 1, so walk bucket 1's chain: 50, 85, 92. Found after 3 comparisons. To find 15: 15 mod 7 = 1, walk 50, 85, 92, reach null: not found. Buckets 0, 2, 3, 4, 5, and 6 are never touched.
Mistake: stopping at the first node
Situation: a student searches for 92. They compute bucket 1, see 50 at the front, and say "not found".
What went wrong: a bucket can hold many keys. Seeing a different key at the front means "keep walking", not "not found".
Fix: walk the whole chain until you find the key or reach null.
Separate chaining in Java
The table is an array of chain heads: Node[] table = new Node[m];. Each Node holds a key and next, like the nodes in the Linked lists lesson. Every bucket starts as null (no chain yet). Step through insert(92) when bucket 1 already holds 50 and 85:
Walkthrough: insert(92)
Use Next to go step by step, or tap a line to jump to it.
key = 92
The table already holds 700, 50, 76 and 85 (bucket 1 has a chain of two). We call insert(92).
1void insert(int key) { 2 int i = key % m; // which bucket? 3 Node newNode = new Node(key); 4 if (table[i] == null) { // empty bucket 5 table[i] = newNode; 6 return; 7 } 8 Node current = table[i]; 9 while (current.next != null) { // walk to the last node10 current = current.next;11 }12 current.next = newNode; // add at the end of the chain13} Here is how to read the key lines aloud:
| Code | Read it as |
|---|---|
int i = key % m; | "the bucket number is the remainder of key divided by m." |
if (table[i] == null) | "if bucket i has no chain yet..." |
while (current.next != null) | "while current is not the last node..." |
current.next = newNode; | "link the new node after the last one." |
Part 5 · Method 2
Open addressing: linear probing
Chaining needs a node, and memory for its link, for every key. Open addressing keeps everything inside the array itself: one key per slot, no chains. When a slot is taken, it looks for another one.
Think of numbered parking
Each car's number decides its parking slot. Car 92's slot is 92 mod 7 = 1. Slot 1 is taken, so the driver tries slot 2: taken too. Slot 3 is free, so the car parks there. Anyone looking for car 92 later starts at slot 1 and walks the same way.
Open addressing and probing
In open addressing, every key is stored in the table itself, at most one key per slot. On a collision, the table tries other slots in a fixed order until it finds a free one. Each try is called a probe, and the order of tries is the probe sequence.
Linear probing
In linear probing, try number i goes to slot (h(k) + i) mod m, for i = 0, 1, 2, …
So the tries are h(k), then the next slot, then the next, one by one. Try i = 0 is the key's home slot, h(k).
Why "mod m" again? If the tries pass the last slot (m − 1), mod m brings them back around to slot 0, like a clock. Step through the same six keys:
Linear probing, by hand (m = 7)
Use Next to go through the steps.
An empty table with 7 slots (0 to 6). Each slot holds at most one key. We insert 50, 700, 76, 85, 92, 73 in order, with linear probing.
On paper, list every key's tries. The number badges in the pictures are the try numbers i:
| Key | h(k) = k mod 7 | Slots tried | Goes into |
|---|---|---|---|
| 50 | 1 | 1 | 1 |
| 700 | 0 | 0 | 0 |
| 76 | 6 | 6 | 6 |
| 85 | 1 | 1, 2 | 2 |
| 92 | 1 | 1, 2, 3 | 3 |
| 73 | 3 | 3, 4 | 4 |
Clusters
Look at the final table: slots 0 to 4 are one solid block. Any new key whose home slot is anywhere in that block has to walk to the end of it, and then it makes the block longer. Try 69: 69 mod 7 = 6, and slot 6 is taken, so it wraps around to 0, 1, 2, 3, 4 (all taken) and lands in slot 5 after 7 tries.
These blocks are called clusters (books say primary clustering). They are linear probing's big weakness, and the reason for the methods in Part 6.
Searching: follow the same path
A search must take the same tries the insert took. It stops in one of two ways: it finds the key, or it reaches an empty slot. An empty slot means the key was never inserted: if it had been, it would have stopped there at the latest.
In Java, the table is an int[]. Since keys are 0 or more, the value -1 can stand for "empty" (named EMPTY). Step through find(92), then find(15):
Walkthrough: find(92) and find(15)
Use Next to go step by step, or tap a line to jump to it.
key = 92
This is the table we built with linear probing. We call find(92). The method gives back the slot where 92 is, or -1 (like the searches in the Searching lesson).
1int find(int key) { 2 int home = key % m; 3 for (int i = 0; i < m; i++) { 4 int slot = (home + i) % m; // linear probing 5 if (table[slot] == EMPTY) { 6 return -1; // a never-used slot: stop 7 } 8 if (table[slot] == key) { 9 return slot; // found it10 }11 }12 return -1; // tried every slot13} Here is how to read the key lines aloud:
| Code | Read it as |
|---|---|
int slot = (home + i) % m; | "try number i: i slots after home, wrapping around past the end." |
if (table[slot] == EMPTY) | "if this slot was never used, the key cannot be further along: stop." |
return slot; | "found: give back the slot number." |
Deleting: leave a mark
Deleting from a chain is easy: unlink the node. In open addressing, it is tricky, because other keys may have probed past the slot you want to empty. Step through deleting 85:
Deleting with linear probing
Use Next to go through the steps.
First, a normal search. 92 belongs in slot 1, but when it was inserted, slots 1 and 2 were taken, so it moved on to slot 3. A search must follow the same path: slot 1 (50, not it), slot 2 (85, not it), slot 3: found.
Deleted mark (tombstone)
When a key is removed in open addressing, its slot gets a deleted mark instead of becoming empty. A search treats the mark as "something was here: keep going". An insert may reuse the slot as a free one.
Both tables in one program
Here is the chaining table and the linear-probing table in one runnable file, with insert, search, remove, and print. Run it and match the output to the pictures above:
1class Node {
2 int key;
3 Node next;
4
5 Node(int key) {
6 this.key = key;
7 }
8}
9
10class ChainTable {
11 Node[] table;
12 int m;
13
14 ChainTable(int m) {
15 this.m = m;
16 table = new Node[m]; // every bucket starts as null
17 }
18
19 void insert(int key) {
20 int i = key % m; // which bucket?
21 Node newNode = new Node(key);
22 if (table[i] == null) { // empty bucket
23 table[i] = newNode;
24 return;
25 }
26 Node current = table[i];
27 while (current.next != null) { // walk to the last node
28 current = current.next;
29 }
30 current.next = newNode; // add at the end of the chain
31 }
32
33 boolean contains(int key) {
34 Node current = table[key % m]; // only this bucket's chain
35 while (current != null) {
36 if (current.key == key) {
37 return true;
38 }
39 current = current.next;
40 }
41 return false;
42 }
43
44 void print() {
45 for (int i = 0; i < m; i++) {
46 System.out.print(i + ":");
47 Node current = table[i];
48 while (current != null) {
49 System.out.print(" " + current.key);
50 current = current.next;
51 }
52 System.out.println();
53 }
54 }
55}
56
57class ProbeTable {
58 static final int EMPTY = -1; // never used
59 static final int DELETED = -2; // a key was removed here
60 int[] table;
61 int m;
62
63 ProbeTable(int m) {
64 this.m = m;
65 table = new int[m];
66 for (int i = 0; i < m; i++) {
67 table[i] = EMPTY;
68 }
69 }
70
71 boolean insert(int key) {
72 int home = key % m;
73 for (int i = 0; i < m; i++) {
74 int slot = (home + i) % m; // linear probing
75 if (table[slot] == EMPTY || table[slot] == DELETED) {
76 table[slot] = key;
77 return true;
78 }
79 }
80 return false; // no free slot
81 }
82
83 int find(int key) {
84 int home = key % m;
85 for (int i = 0; i < m; i++) {
86 int slot = (home + i) % m; // linear probing
87 if (table[slot] == EMPTY) {
88 return -1; // a never-used slot: stop
89 }
90 if (table[slot] == key) {
91 return slot; // found it
92 }
93 }
94 return -1; // tried every slot
95 }
96
97 void remove(int key) {
98 int slot = find(key);
99 if (slot != -1) {
100 table[slot] = DELETED; // a mark, not EMPTY
101 }
102 }
103
104 void print() {
105 for (int i = 0; i < m; i++) {
106 if (table[i] == EMPTY) {
107 System.out.println(i + ": -");
108 } else if (table[i] == DELETED) {
109 System.out.println(i + ": deleted");
110 } else {
111 System.out.println(i + ": " + table[i]);
112 }
113 }
114 }
115}
116
117public class Main {
118 public static void main(String[] args) {
119 int[] keys = {50, 700, 76, 85, 92, 73};
120
121 ChainTable chain = new ChainTable(7);
122 for (int i = 0; i < keys.length; i++) {
123 chain.insert(keys[i]);
124 }
125 chain.print();
126 System.out.println(chain.contains(92)); // true
127 System.out.println(chain.contains(15)); // false
128
129 ProbeTable probe = new ProbeTable(7);
130 for (int i = 0; i < keys.length; i++) {
131 probe.insert(keys[i]);
132 }
133 probe.print();
134 System.out.println(probe.find(92)); // 3
135 probe.remove(85);
136 System.out.println(probe.find(92)); // still 3
137 System.out.println(probe.find(85)); // -1
138 }
139} ▶ This program runs as is. Try it in the Programiz online compiler: tap Copy, paste it there, and press Run.
Output:
0: 700
1: 50 85 92
2:
3: 73
4:
5:
6: 76
true
false
0: 700
1: 50
2: 85
3: 92
4: 73
5: -
6: 76
3
3
-1 Two new lines to read aloud:
| Code | Read it as |
|---|---|
static final int EMPTY = -1; | "EMPTY is a constant (a fixed value with a name) equal to -1." final means it can never change. Keys are 0 or more, so -1 can never be a real key. |
table[slot] == EMPTY || table[slot] == DELETED | "the slot was never used, or its key was removed": either way, insert may use it. |
Good to know: these tables assume every key is different, like student IDs. A real table would first search for the key and skip it if it is already there. Negative keys need Math.floorMod(key, m), because Java's % gives a negative remainder for a negative number.
Part 6 · Methods 3 and 4
Quadratic probing and double hashing
Linear probing's trouble is that colliding keys all walk the same path, one slot at a time, so they pile up into clusters. The fix: jump farther on each try. Two ways to do that:
Quadratic probing
Quadratic probing
In quadratic probing, try number i goes to slot (h(k) + i²) mod m.
The jumps from home grow: 0, 1, 4, 9, 16, … slots away ("quadratic" means "with a square").
Same keys, same table:
Quadratic probing, by hand (m = 7)
Use Next to go through the steps.
An empty table with 7 slots (0 to 6). Each slot holds at most one key. We insert 50, 700, 76, 85, 92, 73 in order, with quadratic probing.
| Key | h(k) | Slots tried | Goes into |
|---|---|---|---|
| 50 | 1 | 1 | 1 |
| 700 | 0 | 0 | 0 |
| 76 | 6 | 6 | 6 |
| 85 | 1 | 1, 2 | 2 |
| 92 | 1 | 1, 2, 5 | 5 |
| 73 | 3 | 3 | 3 |
See 92: its third try jumped from slot 1 straight to slot 5 (1 + 2² = 5), out of the crowd.
Watch out: quadratic probing can miss free slots
Because the jumps skip slots, the tries can keep landing on the same few taken slots while another slot is still free. In the lab in Part 7, tap all the same h with Quadratic: 18 and 23 cannot be placed, even though slots 0 and 1 are empty. (With Double, all five fit.)
The safe rule: use a prime m and keep the table at most half full. Then quadratic probing always finds a free slot.
Double hashing
Double hashing
In double hashing, a second hash function gives each key its own jump size, its step. Try number i goes to slot (h(k) + i × step(k)) mod m.
Two keys with the same home slot usually have different steps, so they stop following each other.
Course convention
The step is step(k) = R − (k mod R), where R is the largest prime smaller than m (m = 7 → R = 5; m = 5 → R = 3). It is always between 1 and R, so it is never 0: a step of 0 would try the same slot forever. Books use different second functions, so on a quiz or exam, always use the one the question gives.
With m = 7, R = 5. Same keys again:
Double hashing, by hand (m = 7, R = 5)
Use Next to go through the steps.
An empty table with 7 slots (0 to 6). Each slot holds at most one key. We insert 50, 700, 76, 85, 92, 73 in order, with double hashing.
| Key | h(k) | step(k) = 5 − (k mod 5) | Slots tried | Goes into |
|---|---|---|---|---|
| 50 | 1 | 5 | 1 | 1 |
| 700 | 0 | 5 | 0 | 0 |
| 76 | 6 | 4 | 6 | 6 |
| 85 | 1 | 5 | 1, 6, 4 | 4 |
| 92 | 1 | 3 | 1, 4, 0, 3 | 3 |
| 73 | 3 | 2 | 3, 5 | 5 |
The three results side by side
In this tiny example, double hashing needed the most tries. With only 7 slots and 6 keys, every method runs into full slots. The payoff of quadratic probing and double hashing shows in big tables, where linear probing's clusters keep growing and the other two scatter the keys.
Here is a summary of the four methods:
| Method | Try i goes to | Good | Watch out |
|---|---|---|---|
| Separate chaining | always bucket h(k) (add to its chain) | simple; never "full"; easy delete | extra memory for nodes and links |
| Linear probing | (h + i) mod m | simple; no extra memory | clusters |
| Quadratic probing | (h + i²) mod m | breaks up clusters | can miss free slots (use prime m, at most half full) |
| Double hashing | (h + i × step(k)) mod m | each key has its own path | two hash functions to compute; step must never be 0 |
Part 7 · Try it
The hash table lab
Put any keys into a table with any of the four methods, one key per step. Things to try:
- The lesson keys with all four methods: compare the "tries in all".
- wrap around with Linear: watch 17 go past slot 4 and back to slot 0.
- prices (m = 10), then the same keys with m = 7 or 11.
- all the same h with Quadratic, then with Double.
Hash table lab
Tap a method to insert the keys. Use Next step to watch one key at a time. Change m or the keys and the lab runs again.
Quick tries:
Color key
Part 8 · When the table fills up
Load factor and rehashing
The more keys a table holds, the more collisions there are, and the more tries each insert and search needs. To know when a table is getting too crowded, we measure how full it is.
Load factor
The load factor is α = n / m ("alpha"): n is the number of keys stored, and m is the number of slots. Our lesson table has 6 keys in 7 slots: α = 6/7 ≈ 0.86.
It means slightly different things for the two families:
- Separate chaining: α is the average chain length. It can go above 1 (more keys than buckets), but long chains are slow.
- Open addressing: α can never go above 1, because each slot holds one key. At α = 1 the table is full.
For linear probing in a big table, here is about how many tries a search takes on average (from the formulas in Sedgewick and Wayne's Algorithms):
| Load factor α | Search, key is there | Search, key is not there |
|---|---|---|
| 0.5 (half full) | 1.5 tries | 2.5 tries |
| 0.75 | 2.5 tries | 8.5 tries |
| 0.9 | 5.5 tries | 50.5 tries |
At half full, it is still about one or two tries. Near full, it gets slow fast. So tables are kept from getting too full: open addressing usually aims for α ≤ 0.5, and Java's HashMap (chaining) grows when α passes 0.75.
Rehashing
Rehashing means making a bigger table (often about twice as big, and a prime) and inserting every key again with the new m.
Why not just copy the keys over to the same slot numbers? Because every slot number came from k mod m, and m has changed. In the bigger table, the keys belong in new places:
Rehashing takes work (every key is inserted again), but it happens rarely: after doubling, many more inserts fit before the next one. So, on average, inserts stay fast.
Part 9 · Why this matters
How fast is a hash table?
Here is how finding one key compares, for n keys:
| Where the keys are | Find, usually (average case) | Find, worst case | Keeps keys in order? |
|---|---|---|---|
| Unsorted array (linear search) | up to n checks | n | no |
| Sorted array (binary search) | about log₂ n (20 for a million) | about log₂ n | yes |
| Balanced BST (AVL, red-black) | about log₂ n | about log₂ n | yes (in-order) |
| Hash table | 1 or 2 tries, if the load factor is kept low | n | no |
Books call the "usually" column the average case: ordinary keys, a good hash function, and a low load factor. The worst case happens when (almost) every key collides: one long chain, or one long cluster. Then a search is as slow as linear search: with 1,000 keys all in one bucket, it may check all 1,000. Remember the prices in a table of 10 . A good hash function, a prime m, and rehashing keep that from happening in practice.
Notice the last column: a hash table scatters the keys on purpose, so it cannot list them in order, or quickly find the smallest. Here is how to choose:
| You need to... | Good choice |
|---|---|
| find, add, and remove by exact key, many times (ID → student record, username → account) | hash table (HashMap, HashSet) |
| keep keys sorted, find the smallest or largest, or list a range (all grades from 80 to 90) | balanced BST (TreeMap, TreeSet) |
| store a few values, or values by position | array or list |
Java's own HashMap and HashSet use separate chaining (see the chaining picture in Part 4 ), and double their table size when the load factor passes 0.75.
Good to know: books write the hash table's usual speed as O(1) on average ("constant": it does not grow with n), and its worst case as O(n). You will meet these names again when the course analyzes algorithms.
Part 10 · Your turn
Practice
On paper
- m = 5, h(k) = k mod 5. Insert the keys 12, 27, 33, 17, 40, in this order, using (a) separate chaining, (b) linear probing, (c) quadratic probing, (d) double hashing with step(k) = 3 − (k mod 3). For (b) to (d), write a table of tries like the ones in Parts 5 and 6. The first row of (b) is done for you:
Key h(k) Slots tried Goes into 12 2 2 2 - Use your linear-probing table from 1(b). (a) Which slots does a search for 40 check? (b) Which slots does a search for 22 check, and what does it return?
- (a) What is the load factor of the table in 1(b)? (b) Rehash its keys into a table with m = 11, using linear probing.
- Spot the bug. This program should insert four keys and print "done". What happens instead, and how do you fix it?
1public class Main { 2 static int m = 5; 3 static int[] table = {-1, -1, -1, -1, -1}; 4 5 static void insert(int key) { 6 int home = key % m; 7 for (int i = 0; i < m; i++) { 8 int slot = home + i; 9 if (table[slot] == -1) { 10 table[slot] = key; 11 return; 12 } 13 } 14 } 15 16 public static void main(String[] args) { 17 insert(12); 18 insert(27); 19 insert(33); 20 insert(17); 21 System.out.println("done"); 22 } 23} - Hash table or not? (a) A canteen card system looks up a student's balance by card number, thousands of times a day. (b) The registrar must print the Dean's List from highest grade to lowest. (c) A linear-probing table is 90% full and searches are getting slow.
Show the answers
For self-study, not for grading. All tables were checked by running the code in Java.
- (a) Separate chaining:
(b) Linear probing:0: 40 1: 2: 12 → 27 → 17 3: 33 4:Key h(k) Slots tried Goes into 12 2 2 2 27 2 2, 3 3 33 3 3, 4 4 17 2 2, 3, 4, 0 0 40 0 0, 1 1
17 wraps around: slots 2, 3, 4 are taken, so (2 + 3) mod 5 = 0.0: 17 1: 40 2: 12 3: 27 4: 33
(c) Quadratic probing:Key h(k) Slots tried Goes into 12 2 2 2 27 2 2, 3 3 33 3 3, 4 4 17 2 2, 3, 1 1 40 0 0 0
17: (2 + 0) = 2 taken, (2 + 1) = 3 taken, (2 + 4) mod 5 = 1 free.0: 40 1: 17 2: 12 3: 27 4: 33
(d) Double hashing, step(k) = 3 − (k mod 3):Key h(k) step(k) Slots tried Goes into 12 2 3 2 2 27 2 3 2, 0 0 33 3 3 3 3 17 2 1 2, 3, 4 4 40 0 2 0, 2, 4, 1 1 0: 27 1: 40 2: 12 3: 33 4: 17 - (a) 40 mod 5 = 0: slot 0 (17, not it), slot 1 (40): found after 2 tries. (b) 22 mod 5 = 2: slots 2, 3, 4, 0, 1 all hold other keys and none is empty, so the loop tries all 5 slots and returns -1. A full table makes every "not found" search check every slot.
- (a) α = 5 / 5 = 1.0: completely full. (b) With mod 11:
All the other slots are empty, and no key collides.0: 33 1: 12 5: 27 6: 17 7: 40 - It crashes while inserting 17:
ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5. 17 mod 5 = 2; slots 2, 3, and 4 are taken, so the next try is slot 2 + 3 = 5, which does not exist. Line 8 forgot to wrap around. Fix:int slot = (home + i) % m;. - (a) Hash table: many lookups by exact key. (b) Not a hash table: it has no order. Use a sorted structure (a BST or
TreeMap), or sort the grades. (c) Rehash into a bigger table (about twice the size, a prime), so the load factor drops below 0.5.
Your coding task: quadratic probing
This small table uses an array of 5 slots, with -1 for empty. Finish insert so it uses quadratic probing: write about 5 lines where it says YOUR CODE, then run it. It should print the same table as your answer to Problem 1(c), then false (the table is full, so 99 cannot go in).
1public class Main {
2 static int m = 5;
3 static int[] table = {-1, -1, -1, -1, -1}; // -1 = empty
4
5 static boolean insert(int key) {
6 int home = key % m;
7 for (int i = 0; i < m; i++) {
8 // YOUR CODE: work out the slot with QUADRATIC probing,
9 // and if that slot is empty (-1), store the key there
10 // and return true.
11
12 }
13 return false; // no free slot found
14 }
15
16 public static void main(String[] args) {
17 int[] keys = {12, 27, 33, 17, 40};
18 for (int i = 0; i < keys.length; i++) {
19 insert(keys[i]);
20 }
21 for (int i = 0; i < m; i++) {
22 System.out.println(i + ": " + table[i]);
23 }
24 System.out.println(insert(99));
25 }
26} ▶ This program runs as is. Try it in the Programiz online compiler: tap Copy, paste it there, and press Run.
Show one possible answer
1static boolean insert(int key) {
2 int home = key % m;
3 for (int i = 0; i < m; i++) {
4 int slot = (home + i * i) % m; // quadratic probing
5 if (table[slot] == -1) {
6 table[slot] = key;
7 return true;
8 }
9 }
10 return false; // no free slot found
11} Output:
0: 40
1: 17
2: 12
3: 27
4: 33
false The only change from linear probing is i * i instead of i. Java has no "squared" operator, so i² is written i * i.
Part 11 · Check yourself
Self-check
Not graded. Tap an answer to see if you got it, and read why.
-
What is the main job of a hash function?
A hash function maps a key to an index from 0 to m − 1. It cannot promise a different number for every key: there are more possible keys than slots, so collisions happen.
-
What is a collision?
A collision is two different keys with the same h(k). Every hash table needs a plan for it: chaining or open addressing.
-
m = 7 and h(k) = k mod 7. Which slot does the key 45 get?
45 = 6 × 7 + 3, so 45 mod 7 = 3.
-
Linear probing, m = 7. Slots 3 and 4 are taken; slot 5 is free. Where does the key 10 go?
10 mod 7 = 3. Try 3 (taken), then 4 (taken), then 5 (free).
-
Quadratic probing, m = 11, h(k) = 2. Which slot is try i = 2?
Try i uses (h + i²) mod m: (2 + 2²) mod 11 = 6. (Linear probing would try 4.)
-
In open addressing, why is a deleted key replaced with a "deleted" mark instead of an empty slot?
A search stops at an empty slot. If a deleted key's slot looked empty, a key that probed past it would become unreachable. The mark says "keep looking".
-
A table has m = 10 slots and holds 8 keys. What is its load factor?
Load factor = n / m = 8 / 10 = 0.8. For open addressing, that is getting crowded: time to think about rehashing.
-
Why is a prime number (like 7, 11, 101) a good table size for h(k) = k mod m?
With m = 10, k mod 10 only looks at the last digit, so 10, 20, 30, … all collide. A prime m uses the whole key, so patterned keys spread out. Collisions can still happen.
-
A chaining table holds 1,000 keys, and a bad hash function put all of them in bucket 0. In the worst case, how many keys does a search check?
All 1,000 keys are in one chain, so the search walks it like a linked list: up to 1,000 checks, as slow as linear search. This is the hash table's worst case, n. With a good hash function and a low load factor, the average is about 1 or 2 tries.
Score: 0 / 9
Key takeaways
- Hashing turns a key into a slot number with a fixed rule, so a search can jump straight to one slot.
- In this course, h(k) = k mod m. A prime m spreads keys better.
- Collisions cannot be avoided. Separate chaining keeps a chain per bucket; open addressing tries other slots.
- Try i: linear (h + i) mod m; quadratic (h + i²) mod m; double hashing (h + i × step(k)) mod m.
- Searches follow the insert's path and stop at the key or at an empty slot, so deletes leave a "deleted" mark.
- Load factor α = n / m. Keep it low, and rehash into a bigger table when it gets high. Then finding a key takes about 1 or 2 tries on average; in the worst case (every key collides) it takes up to n. A hash table keeps no order.
Next: Algorithm complexity analysis, the last lesson of the course (coming soon). It brings together the Big-O names you met in searching, sorting, and hashing. The Maps lesson shows how Java's HashMap turns String keys into numbers first.
References
- T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein, Introduction to Algorithms, 4th ed. MIT Press, 2022, Chapter 11 (Hash tables; the division method; open addressing).
- R. Sedgewick and K. Wayne, Algorithms, 4th ed. Addison-Wesley, 2011, Section 3.4 (hash tables; average tries for linear probing). algs4.cs.princeton.edu
- M. T. Goodrich, R. Tamassia, and M. H. Goldwasser, Data Structures and Algorithms in Java, 6th ed. Wiley, 2014, Section 10.2 (double hashing with q − (k mod q)).
- Programiz, "Hash Table." programiz.com/dsa/hash-table
- Oracle, "HashMap (Java SE 21 & JDK 21)," Java API documentation (load factor 0.75). docs.oracle.com
- ITC 120 Course Syllabus (Hashing Algorithms), Davao Oriental State University.