CS 411 Fall 2025  >  Outline & Supplemental Notes for October 20, 2025


CS 411 Fall 2025
Outline & Supplemental Notes
for October 20, 2025

Outline

Hashing [L 7.3]

Supplemental Notes

Good Hash Functions

The text lists two important properties of a good hash function.

I will add a third that is sometimes important.

Real-world input often has structure. When we use open hashing, we do not want such structure to cause many keys to get the same hash address. When we use closed hashing, this third property can be less important; we might just let a good probe sequence (see below) deal with the problem.

Probe Sequences

Linear probing means that, if \(k\) is the hash address, then the probe sequence is

\[ k, k+1, k+2, k+3, k+4, \dots. \]

An important probe sequence that the text does not mention is quadratic probing. In this probe sequence, we add consecutive squares to the hash address:

\[ k, k+1, k+4, k+9, k+16, \dots. \]

Quadratic probing greatly reduces the formation of clusters.

Analysis

CRUD Efficiency

I would like to be more precise than the text about the time efficiency of hash-table CRUD operations.

First, consider worst-case time. There are generally many more possible keys than there are locations in the table. It is thus possible that all keys will be given the same hash address. If separate chaining is used, this means that every key will be stored in the same bucket, and so a search for a given key may require looking at every key in the table. If closed hashing is used, this means that a search for a given key may require a probe of every item in the table. In either case, we see that all CRUD operations are \(\Theta(n)\) time.

Now consider average-case time. Suppose for the moment that we are using separate chaining. An unsuccessful search averages \(\alpha\) comparisons, while a successful search averages about \(1+\frac{\alpha}{2}\) comparisons. In either case, it is constant-time on average, assuming we keep the value of \(\alpha\) low. That takes care of the Read and Update operations. The Delete operation requires the additional operation of removing a node from a linked list, after the node has been found; it is also constant-time on average.

Inserting a new key (the Create operation) is trickier. This may raise \(\alpha\) unacceptably high, resulting in rehashing, which requires calling the hash function for every key in the table, a \(\Theta(n)\) operation. Since \(\alpha\) depends only on the number of items in the table, and not on their values, the Create operation is linear-time even on average.

Just as with inserting into a smart array, the linear-time operation usually does not need to happen very often. In a well written Hash Table, the average time for a large number of consecutive insertions of average data, will be constant-time. That is, for repeated insertions, Create is amortized constant-time for average data.

It should be noted that the previous sentence is only true if both kinds of averages are done. The Create operation is not amortized constant-time, since every key may lie in the same bucket, making every insertion require a linear number of steps. The Create operation is also not constant-time for average data, as even average data will eventually require rehashing, a linear-time operation. However, Create is amortized constant-time for average data.

For closed hashing, the analysis is much trickier, but the conclusions are essentially the same.

“Rare” Behavior

The text notes that worst-case behavior is rare in a well written Hash Table & hash function. This is correct; however, exactly what “rare” means is worth thinking about. In particular, if our input is provided by a malicious user who can produce data that leads to worst-case hash-table behavior, then it may not matter how rare such data is.

Such concerns are not merely academic. For example, in 2003 security researchers Scott A. Crosby and Dan S. Wallach showed that by feeding carefully selected data to the Bro network intrusion detection system, they could cause poor behavior in a Hash Table, rendering the intrusion detection ineffective.

Hashing in Practice

It is somewhat surprising that there is no consensus on what the best Hash Table implementation is.

A little searching and reading suggests that open hashing is more popular than closed hashing for ordinary in-memory associative data. Apparently, the built-in Hash Tables for the Perl and Ruby programming languages use open hashing. In the C++ Standard Library, the interface to std::unordered_map includes functionality giving client code access to specific buckets. Thus, implementations would seem to be required to use open hashing. Regardless, a quick check shows that the GNU implementation of the C++11 Standard Library does in fact use open hashing in std::unordered_map (v. 4.8.1 checked).

On the other hand, the built-in Hash Table in the standard implementation of the Python programming language uses closed hashing (CPython v3.3 checked). Little effort is made to avoid patterned output from the hash function. The table size is always a power of \(2\), and the load factor is kept under \(2/3\). The probe sequence used is illustrated by the following code.

[C++]

size_t hash_addr;   // Hash address
size_t table_size;  // Locations in table; ALWAYS POWER OF 2

size_t perturb = hash_addr;
size_t i = hash_addr % table_size;
while (!probe(i))  // Probe @ index i; returns true on success
{
    i = (5*i + 1 + perturb) % table_size;
    perturb >>= 5;
}
// Now i is the index where the value is stored