Lecture 08: Hash Collision Resolutions
CSE 373: Data Structures and Algorithms
1
CSE 373 24WI
CSE 373 24WI
1
Warm Up
int hashCode (int key) {
return mapToValidIndex((key / 100));
}
(1,‘a’)(5,‘b’)(100,‘c’)(555,‘d’)(1234,‘e’)(8675,‘f’)(309,‘g’)(12345, ‘h’)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| | | | | | | | | |
(1234, ‘e’)
(5, b)
(12345, ‘h’)
(1, a)
(100, ‘c’)
(309, ‘g’)
(8675, ‘f’)
(555, ‘c’)
CSE 373 24WI
2
Announcements
CSE 373 24WI
3
Reducing Collisions - Resizing
What if we used the same array with 10 buckets, but continued to add data until we had 100 entries?
CSE 373 24WI
4
Reducing Collisions - Resizing
If array.length is fixed as n increases then
get(key) = O(n/array.length) ∊ O(n)
BUT if you resize the array when when n / array.length = 1 then get(key) = O(n/array.length) ∊ O(1)
You must resize and re-hash for Project 2!
PRO TIP: When you resize, choose a table length that will help reduce collisions if you multiply the array length by 2 and then choose the nearest prime number
CSE 373 24WI
5
Resizing
Don’t forget to re-hash your keys!
Project 2
0 | |
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
(7,blue) | |
(4,orange) | |
0 | |
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
(1,red) | |
(22,tan) | / |
(22,tan) | |
(7,blue) | |
(77,aqua) | |
(4,orange) | |
(1,red) | |
(6,pink) | |
(8,lilac) | |
(53,puce) | |
(6,pink) | |
(77,aqua) | |
(53,puce) | |
(8,lilac) | |
If we just expand the buckets array, several values are hashed in the wrong place
How to Resize:
CSE 373 24WI
6
Questions?
CSE 373 24WI
7
What about non integer keys?
Hash function definition
A hash function is any function that can be used to map data of arbitrary size to fixed-size values.
Let’s define another hash function to change stuff like Strings into ints!
Best practices for designing hash functions:
Avoid collisions
Low computational costs
CSE 373 24WI
8
Practice
Consider a StringDictionary using separate chaining with an internal capacity of 10. Assume our buckets are implemented using a LinkedList. Use the following hash function:
public int hashCode(String input) {
return input.length() % arr.length;
}
Now, insert the following key-value pairs. What does the dictionary internally look like?
(“a”, 1) (“ab”, 2) (“c”, 3) (“abc”, 4) (“abcd”, 5) (“abcdabcd”, 6) (“five”, 7) (“hello world”, 8)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| | | | | | | | | |
(“a”, 1)
(“abcd”, 5)
(“c”, 3)
(“five”, 7)
(“abc”, 4)
(“ab”, 2)
(“hello world”, 8)
(“abcdabcd”, 6)
CSE 373 24WI
9
hashCode()
Implementation 1: Simple aspect of values
public int hashCode(String input) {
return input.length();
}
Implementation 2: More aspects of value
public int hashCode(String input) {
int output = 0;
for(char c : input) {
out += (int)c;
}
return output;
}
Implementation 3: Multiple aspects of value + math!
public int hashCode(String input) {
int output = 1;
for (char c : input) {
int nextPrime = getNextPrime();
out *= Math.pow(nextPrime, (int)c);
}
return Math.pow(nextPrime, input.length());
}
Pro: super fast
Con: lots of collisions!
Pro: still really fast
Con: some collisions
Pro: few collisions
Con: slow, gigantic integers
Before we % by length, we have to convert the data into an int
CSE 373 24WI
10
Good Hashing
The hash function of a HashMap gets called a LOT:
This is why it is so important to have a “good” hash function. A good hash function is:
public int hashCode(String s) {
return random.nextInt();
}
public int hashCode(String s) {
int retVal = 0;
for (int i = 0; i < s.length(); i++) {
for (int j = 0; j < s.length(); j++) {
retVal += helperFun(s, i, j);
}
}
return retVal;
}
public int hashCode(String s) {
if (s.length() % 2 == 0) {
return 17;
} else {
return 43;
}
}
NOT deterministic
NOT efficient
NOT uniform
CSE 373 24WI
11
Practice
Which of the following two hashCode functions for a String will produce more collisions on average?
public int hashCode1() {
Iterator<Character> iterator = this.iterator();
int result = 13;
int i = 0;
while (iterator.hasNext()) {
result += iterator.next().hashCode() * 37^i;
i++;
}
return result % 5;
}
public int hashCode2() {
Iterator<Character> iterator = this.iterator();
int result = 0;
int i = 0;
while (iterator.hasNext()) {
result += iterator.next().hashCode();
i++;
}
return i;
}
hashCode1 will produce more collisions because it limits the range of possible values in the return statement. If that %5 was removed than hashCode2 would produce more collisions
CSE 373 24WI
12
Java’s hashCode function
All Java Objects must include a hashCode function:
public int hashCode();
Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap.
The general contract of hashCode is:
CSE 373 24WI
13
Java and Hash Functions
CSE 373 24WI
14
Questions?
CSE 373 24WI
15
Linear Probing
Quadratic Probing
Double Hashing
Summary
CSE 373 24WI
16
Handling Collisions
Solution 2: Open Addressing
Resolves collisions by choosing a different location to store a value if natural choice is already full.
Type 1: Linear Probing
If there is a collision, keep checking the next element until we find an open spot.
int findFinalLocation(Key s) {
int naturalHash = this.hashCode(s);
int index = naturalHash % array.length;
while (index in use) {
i++;
index = (naturalHash + i) % array.length;
}
return index;
}
CSE 373 24WI
17
Linear Probing
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| | | | | | | | | |
Insert the following values into the Hash Table using a hashFunction of % table size and linear probing to resolve collisions
1, 5, 11, 7, 12, 17, 6, 25
1
5
11
7
12
17
6
25
CSE 373 24WI
18
Linear Probing
Primary Clustering
When probing causes long chains of occupied slots within a hash table
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| | | | | | | | | |
Insert the following values into the Hash Table using a hashFunction of % table size and linear probing to resolve collisions
38, 19, 8, 109, 10
38
19
8
109
10
Problem:
CSE 373 24WI
19
Runtime
When is runtime good?
When we hit an empty slot
When is runtime bad?
When we hit a “cluster”
Maximum Load Factor?
λ at most 1.0
When do we resize the array?
λ ≈ ½ is a good rule of thumb
CSE 373 24WI
20
Can we do better?
Clusters are caused by picking new space near the natural index
Solution 2: Open Addressing (still)
Type 2: Quadratic Probing
Instead of checking i past the original location, check i² from the original location
int findFinalLocation(Key s)
int naturalHash = this.hashCode(s);
int index = naturalHash % array.length;
while (index in use) {
i++;
index = (naturalHash + i*i) % array.length;
}
return index;
CSE 373 24WI
21
Linear Probing
Quadratic Probing
Double Hashing
Summary
CSE 373 24WI
22
Quadratic Probing
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| | | | | | | | | |
(49 % 10 + 0 * 0) % 10 = 9
(49 % 10 + 1 * 1) % 10 = 0
89
18
49
Insert the following values into the Hash Table using a hashFunction of % table size and quadratic probing to resolve collisions
89, 18, 49, 58, 79, 27
58
79
(79 % 10 + 0 * 0) % 10 = 9
(79 % 10 + 1 * 1) % 10 = 0
(79 % 10 + 2 * 2) % 10 = 3
Problems:
If λ≥ ½ we might never find an empty spot
Infinite loop!
Can still get clusters
27
Now try to insert 9.
Uh-oh
(58 % 10 + 0 * 0) % 10 = 8
(58 % 10 + 1 * 1) % 10 = 9
(58 % 10 + 2 * 2) % 10 = 2
CSE 373 24WI
23
Quadratic Probing
There were empty spots. What Gives?
Quadratic probing is not guaranteed to check every possible spot in the hash table
The following is true:
Notice we have to assume p is prime to get that guarantee
If the table size is a prime number p, then the first p/2 probes check distinct indices.
CSE 373 24WI
24
Secondary Clustering
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| | | | | | | | | |
Insert the following values into the Hash Table using a hashFunction of % table size and quadratic probing to resolve collisions
19, 39, 29, 9
39
29
19
9
Secondary Clustering
When using quadratic probing, you sometimes need to probe the same sequence of table cells, not necessarily next to one another
CSE 373 24WI
25
Probing
h(k) = the natural hash
h’(k, i) = resulting hash after probing
i = iteration of the probe
T = table size
Linear Probing:
h’(k, i) = (h(k) + i) % T
Quadratic Probing
h’(k, i) = (h(k) + i2) % T
CSE 373 24WI
26
Topics Covered:
Questions?
CSE 373 24WI
27
Linear Probing
Quadratic Probing
Double Hashing
Summary
CSE 373 24WI
28
Double Hashing
Probing causes us to check the same indices over and over- can we check different ones instead?
Use a second hash function!
h’(k, i) = (h(k) + i * g(k)) % T
int findFinalLocation(Key s)
int naturalHash = this.getHash(s);
int index = natrualHash % TableSize;
while (index in use) {
i++;
index = (naturalHash + i*jumpHash(s)) % TableSize;
}
return index;
<- Most effective if g(k) returns value relatively prime to table size
CSE 373 24WI
29
Resizing: Open Addressing
How do we resize? Same as separate chaining
When to resize?
CSE 373 24WI
30
Linear Probing
Quadratic Probing
Double Hashing
Summary
CSE 373 24WI
31
Summary
1. Pick a hash function to:
2. Pick a collision strategy
No clustering
Potentially more “compact” (λ can be higher)
Managing clustering can be tricky
Less compact (keep λ < ½)
Array lookups tend to be a constant factor faster than traversing pointers
CSE 373 24WI
32
Summary
Separate Chaining
Open Addressing
Which one you use depends on your application and what you’re worried about
CSE 373 24WI
33
Java’s HashMap Implementation
CSE 373 24WI
34
Other Hashing Applications
We use it for hash tables but there are lots of uses! Hashing is a really good way of taking arbitrary data and creating a succinct and unique summary of data.
Fingerprinting
git hashes (“identification”)
Ad Tracking
YouTube Content ID
Caching
File Verification / Error Checking
Cryptography
Hashing also ”hides” the data by translating it, this can be used for security
CSE 373 24WI
35
Questions?
CSE 373 24WI
36
That’s all!
CSE 373 24WI
37