We started by
studying the
dictionarydata structure that
store
(key, value)pairs
We defined the
Dictionary interface
public interface Dictionary<K,V>
{
public void put(K k, V v);
public V get(K k);
public V remove(K k);
}
We implemented the
Dictionary interface
using a array
We discovered that:
Searching in a
dictionary is
O(n) when
entries are
unsorted
Searching in a
dictionary is
O(log(n)) when
entries are
sorted
We learned about
hashing that can
speedup
the array operations
by relating
a key k to
an array index using
a
hash value
We will nowimplement the
dictionarydata structure using the
hashing technique...
a.k.a.: a
hash table
The Entry class
for a
hash table using
Separate Chaining
Previously, we
used the followingEntry<K,V> class
in the ArrayMap<K,V>:
public class Entry<K,V>
{
private K key; // Key
private V value; // Value
public Entry(K k, V v) // Constructor
{
key = k;
value = v;
}
... // Methods omitted for brevity
}
We have used this
Entry<sK,V> class
to
implement the
ArrayMapdictionary
data structure
In order to supportseparate chaining
(where each
bucket is
organized as
a linked list):
The Entry<K,V> objects
must contain a
linking field
The Entry class
for a
hash table using
Separate Chaining
The
modifiedEntry<K,V> class
used in a hash table
with
Separate Chaining:
public class Entry<K,V>
{
private K key; // Key
private V value; // Value
private Entry<K,V> next; // Link to create a linked list
public Entry(K k, V v) // Constructor
{
key = k;
value = v;
}
... // Methods omitted for brevity
}
The next variable
is needed in
Separate Chaining to
linkEntry objects
into a linked list
The instance variables used to
implement the
HashTableSP
(Separate Chaining) class
The Hash Table
using Separate Chaining
consists of
an array of
Entry<K,V> objects:
public class HashTableSC<K,V> implements Dictionary<K,V>
{
// Entry contains next to chain into a linked list
private class Entry<K,V>
{
K key; // Key
V value; // Value
Entry<K,V> next; // Chain !
public Entry(K k, V v) // Constructor
{
key = k;
value = v;
}
.... // Other stuff omitted for brevity
}
public Entry<K,V>[] bucket; // The Hash table
public int capacity; // capacity == bucket.length
int NItems; // # items in hash table
// MAD formula: ((a * HashCode + b) % p ) % M
public int MAD_p; // Prime number in the Multiply Add Divide alg
public int MAD_a; // Multiplier in the Multiply Add Divide alg
public int MAD_b; // Offset in the Multiply Add Divide alg
.... // Other stuff omitted for brevity
}
The instance variables used to
implement the
HashTableSP
(Separate Chaining) class
We also
need some variables
to implement the
MAD compression function in
hashing:
public class HashTableSC<K,V> implements Dictionary<K,V>
{
// Entry contains next to chain into a linked list
private class Entry<K,V>
{
K key; // Key
V value; // Value
Entry<K,V> next; // Chain !
public Entry(K k, V v) // Constructor
{
key = k;
value = v;
}
.... // Other stuff omitted for brevity
}
public Entry<K,V>[] bucket; // The Hash table
public int capacity; // capacity == bucket.length
int NItems; // # items in hash table
// MAD formula: ( Math.abs(a * HashCode + b) % p ) % M
public int MAD_p; // Prime number in the Multiply Add Divide alg
public int MAD_a; // Multiplier in the Multiply Add Divide alg
public int MAD_b; // Offset in the Multiply Add Divide alg
.... // Other stuff omitted for brevity
}
The constructor used to
initialize a
HashTableSP
object
Now we must
define a
constructor to
initialize the
instance variables:
public class HashTableSC<K,V> implements Dictionary<K,V>
{
... // Other stuff omitted for brevity
public Entry<K,V>[] bucket; // The Hash table
public int capacity; // capacity == bucket.length
int NItems; // # items in hash table
// MAD formula: ( Math.abs(a * HashCode + b) % p ) % M
public int MAD_p; // Prime number in the Multiply Add Divide alg
public int MAD_a; // Multiplier in the Multiply Add Divide alg
public int MAD_b; // Offset in the Multiply Add Divide alg
public HashTableSC(int M) // Create a hash table of size M
{
bucket = (Entry[]) new Entry[M]; // Create a hash table of size M
capacity = bucket.length; // Capacity of this hash table
NItems = 0; // # items in hash table
MAD_p = 109345121; // We pick this prime number...
MAD_a = 123; // a = non-zero random number
MAD_b = 456; // b = random number
}
....
}
The constructor used to
initialize a
HashTableSP
object
We firstinstantiate the
array
used to store the
entries of the
dictionary:
public class HashTableSC<K,V> implements Dictionary<K,V>
{
... // Other stuff omitted for brevity
public Entry<K,V>[] bucket; // The Hash table
public int capacity; // capacity == bucket.length
int NItems; // # items in hash table
// MAD formula: ( Math.abs(a * HashCode + b) % p ) % M
public int MAD_p; // Prime number in the Multiply Add Divide alg
public int MAD_a; // Multiplier in the Multiply Add Divide alg
public int MAD_b; // Offset in the Multiply Add Divide alg
public HashTableSC(int M) // Create a hash table of size M
{
bucket = (Entry[]) new Entry[M]; // Create a hash table of size M
capacity = bucket.length; // Capacity of this hash table
NItems = 0; // # items in hash table
MAD_p = 109345121; // We pick this prime number...
MAD_a = 123; // a = non-zero random number
MAD_b = 456; // b = random number
}
....
}
The constructor used to
initialize a
HashTableSP
object
Amd theninitialize the
variables for
the MAD compression function:
public class HashTableSC<K,V> implements Dictionary<K,V>
{
... // Other stuff omitted for brevity
public Entry<K,V>[] bucket; // The Hash table
public int capacity; // capacity == bucket.length
int NItems; // # items in hash table
// MAD formula: ( Math.abs(a * HashCode + b) % p ) % M
public int MAD_p; // Prime number in the Multiply Add Divide alg
public int MAD_a; // Multiplier in the Multiply Add Divide alg
public int MAD_b; // Offset in the Multiply Add Divide alg
public HashTableSC(int M) // Create a hash table of size M
{
bucket = (Entry[]) new Entry[M]; // Create a hash table of size M
capacity = bucket.length; // Capacity of this hash table
NItems = 0; // # items in hash table
MAD_p = 109345121; // We pick this prime number...
MAD_a = 123; // a = non-zero random number
MAD_b = 456; // b = random number
}
....
}
The hash function in
hashing
Recall that we use
the followinghash function:
H(k) = H2( H1( k ) )
where:
H1( k ) = the hash code of the key k
H2( x ) = the MAD compression function
The definition of the
hash functionhashValue(k) is
therefore:
/* ==================================================
The hash function for the hash table
================================================== */
public int hashValue(K key)
{
int x = key.hashCode(); // Uses Object.hashCode()
return ((Math.abs(x*MAD_a+MAD_b)%MAD_p)%capacity);
}
Now we are ready to
implementput( ),
get( ) and
remove( )...
but
first -
a help method...
The help method
findEntry(k)
We learned from the
implementation of
ArrayMap that:
Many operations on a
Dictionary
require search
for the entry containing
some key
E.g.:put( ) and
get( ) are
very similar in
ArrayMap !!
Therefore, we
introduce a
helper method to
find the
dictionary entry that
contains a specifickey:
public Entry<K,V> findEntry(K k):
return the reference to the Entry contain key k if found
return null if key k is not found in the dictionary
The help method
findEntry(k)
The helper methodfindEntry(k):
/* ----------------------------------------------------------
findEntry(key): find the Entry containing key in hash table
Returns Entry object containing key if found
Returns null if not found
------------------------------------------------------ */
public Entry findEntry(K k)
{
int hashIdx = hashValue(k); // Get hash index using key k
Entry h = bucket[hashIdx]; // h = head/first of linked list
while (h != null)
{
if ( h.key.equals(k) )
return h;
h = h.next;
}
return null;
}
The help method
findEntry(k)
Find the
bucket that
contains the
key k:
/* ----------------------------------------------------------
findEntry(key): find the Entry containing key in hash table
Returns Entry object containing key if found
Returns null if not found
------------------------------------------------------ */
public Entry findEntry(K k)
{
int hashIdx = hashValue(k); // Get hash index using key k
Entry curr = bucket[hashIdx]; // curr = head/first of linked list
while (curr != null)
{
if ( curr.key.equals(k) )
return curr;
curr = curr.next;
}
return null;
}
The help method
findEntry(k)
Start the
list traversal algorithm on
the
linked list starting at
bucket[hashIdx]:
/* ----------------------------------------------------------
findEntry(key): find the Entry containing key in hash table
Returns Entry object containing key if found
Returns null if not found
------------------------------------------------------ */
public Entry findEntry(K k)
{
int hashIdx = hashValue(k); // Get hash index using key k
Entry<K,V> curr = bucket[hashIdx]; // curr = first of linked list
while (curr != null)
{
if ( curr.key.equals(k) )
return curr;
curr = curr.next;
}
return null;
}
The help method
findEntry(k)
This is just the
classiclist traversal algorithm used to
find a
"node" (entry) containing
k:
/* ----------------------------------------------------------
findEntry(key): find the Entry containing key in hash table
Returns Entry object containing key if found
Returns null if not found
------------------------------------------------------ */
public Entry findEntry(K k)
{
int hashIdx = hashValue(k); // Get hash index using key k
Entry<K,V> curr = bucket[hashIdx]; // curr = first of linked list
while (curr != null)
{
if ( curr.key.equals(k) )
return curr;
curr = curr.next;
}
return null; // Not found
}
The
put(k, v) method
of the hash table using
Separate Chaining
put(k,v):
insert/update the entry(k,v) in the
hash table
public void put(K k, V v)
{
int hashIdx = hashValue(k);
Entry h = findEntry(k);
if ( h != null )
h.value = v; // Update value
else
{
// Add newEntry as first element in list at bucket[hashIdx]
Entry newEntry = new Entry(k, v);
newEntry.next = bucket[hashIdx];
bucket[hashIdx] = newEntry;
NItems++;
}
}
The
put(k, v) method
of the hash table using
Separate Chaining
Find the
entry(k,v) using
the helper function:
public void put(K k, V v)
{
int hashIdx = hashValue(k);
Entry<K,V> h = findEntry(k);
if ( h != null )
h.value = v; // Update value
else
{
// Add newEntry as first element in list at bucket[hashIdx]
Entry newEntry = new Entry(k, v);
newEntry.next = bucket[hashIdx];
bucket[hashIdx] = newEntry;
NItems++;
}
}
The
put(k, v) method
of the hash table using
Separate Chaining
If found,
update the
value in the
entry:
public void put(K k, V v)
{
int hashIdx = hashValue(k);
Entry<K,V> h = findEntry(k);
if ( h != null )
h.value = v; // Update value with v
else
{
// Add newEntry as first element in list at bucket[hashIdx]
Entry newEntry = new Entry(k, v);
newEntry.next = bucket[hashIdx];
bucket[hashIdx] = newEntry;
NItems++;
}
}
The
put(k, v) method
of the hash table using
Separate Chaining
If not found,
insert a
new(k,v)
as the first node
in bucket bucket[hashIdx]:
public void put(K k, V v)
{
int hashIdx = hashValue(k);
Entry<K,V> h = findEntry(k);
if ( h != null )
h.value = v; // Update value with v
else
{
// Add newEntry as first element in list at bucket[hashIdx]
Entry newEntry = new Entry(k, v); // Make new entry
newEntry.next = bucket[hashIdx]; // Point to first in bucket
bucket[hashIdx]
= newEntry; // Make new entry as first
NItems++;
}
}
Note: this is the
addFirst( ) algorithm that you
have learned in
linked list !
The variable
bucket[hashIdx]represents the first variable
of a linked list
The
get(k) method
of the hash table using
Separate Chaining
get(k):
retrieve the
value vassociated with
the key k
from the hash table:
public V get(K k)
{
Entry<K,V> h = findEntry(k);
if ( h != null )
return h.value;
else
return null;
}
The
get(k) method
of the hash table using
Separate Chaining
Find the
entry using the
key k:
public V get(K k)
{
Entry<K,V> h = findEntry(k);
if ( h != null )
return h.value;
else
return null;
}
The
get(k) method
of the hash table using
Separate Chaining
If the entry is
found,
return the
correspondingvalue,
otherwise return
null:
public V get(K k)
{
Entry<K,V> h = findEntry(k);
if ( h != null )
return h.value;
else
return null;
}
The
remove(k) method
of the hash table using
Separate Chaining
remove(k):
remove the
entry (k,v)
from the hash table:
public V remove(K k)
{
int hashIdx = hashValue(k);
// General case: search in list
Entry previous = bucket[hashIdx];
Entry current = bucket[hashIdx];
while (current != null)
{
if ( current.key.equals(k) )
{
previous.next = current.next; // Unlink current
NItems--;
return current.value; // Return value
}
previous = current;
current = current.next;
}
return null;
}
The
remove(k) method
of the hash table using
Separate Chaining
Find the
hash bucket for
the key k:
public V remove(K k)
{
int hashIdx = hashValue(k);
// General case delete from linked list
Entry previous = bucket[hashIdx];
Entry current = bucket[hashIdx];
while (current != null)
{
if ( current.key.equals(k) )
{
previous.next = current.next; // Unlink current
NItems--;
return current.value; // Return value
}
previous = current;
current = current.next;
}
return null;
}
The
remove(k) method
of the hash table using
Separate Chaining
Start a
list traversal at the
first element
in the linked list:
public V remove(K k)
{
int hashIdx = hashValue(k);
// General case delete from linked list
Entry<K,V> previous = bucket[hashIdx];
Entry<K,V> current = bucket[hashIdx];
while (current != null)
{
if ( current.key.equals(k) )
{
previous.next = current.next; // Unlink current
NItems--;
return current.value; // Return value
}
previous = current;
current = current.next;
}
return null;
}
The
remove(k) method
of the hash table using
Separate Chaining
Find the
node containing
the key kwhile remembering
the previous node:
public V remove(K k)
{
int hashIdx = hashValue(k);
// General case delete from linked list
Entry<K,V> previous = bucket[hashIdx];
Entry<K,V> current = bucket[hashIdx];
while (current != null && !current.key.equals(k) )
{
previous = current;
current = current.next;
}
if ( current != null )
{
previous.next = current.next; // Unlink current
NItems--; // Accounting...
return current.value; // Return value
}
return null; // Not found
}
The
remove(k) method
of the hash table using
Separate Chaining
If found,
unlink the
node and
return its
value:
public V remove(K k)
{
int hashIdx = hashValue(k);
// General case delete from linked list
Entry<K,V> previous = bucket[hashIdx];
Entry<K,V> current = bucket[hashIdx];
while (current != null && !current.key.equals(k) )
{
previous = current;
current = current.next;
}
if ( current != null )
{
previous.next = current.next; // Unlink current
NItems--; // Accounting...
return current.value; // Return value
}
return null; // Not found
}
The
remove(k) method
of the hash table using
Separate Chaining
Otherwise,
returnnull:
public V remove(K k)
{
int hashIdx = hashValue(k);
// General case delete from linked list
Entry<K,V> previous = bucket[hashIdx];
Entry<K,V> current = bucket[hashIdx];
while (current != null && !current.key.equals(k) )
{
previous = current;
current = current.next;
}
if ( current != null )
{
previous.next = current.next; // Unlink current
NItems--; // Accounting...
return current.value; // Return value
}
return null; // Not found
}
Demo:
put( ) and
get( )
public static void main(String[] args)
{
Dictionary<String,String> H = new HashTableSC<>(5);
H.put("ice", "cold");
H.put("fire", "hot");
H.put("rock", "hard");
H.put("wool", "soft");
H.put("sun", "hot");
H.put("sun", "**bright**"); // Updates the value !
H.put("moon", "shine");
System.out.println("\n**** Test get(): ****");
System.out.println("ice:" + H.get("ice"));
System.out.println("fire:" + H.get("fire"));
}