哈希表提供了一种有效插入和检索数据的方法(通常在常数/O(1) 时间内)。为此,我们使用一个非常大的数组来存储目标值和一个散列函数,该函数通常将目标值映射为散列值,散列值只不过是这个大数组中的有效索引。完美散列要存储到唯一键(或表中的索引)中的值的散列函数称为完美散列函数。但在实践中,为了存储这些没有已知方法来获得唯一哈希值(表中的索引)的值,我们通常使用哈希函数,该函数可以将每个值映射到特定索引,以便将冲突保持在最低限度。这里的冲突意味着要存储在哈希表中的两个或多个项目映射到相同的哈希值。
现在来回答最初的问题,即:
“设计一个哈希表,你可以使用任何你想要的数据结构。我想看看你如何实现 O(1) 查找时间”。最后他说这更像是通过另一个数据结构来模拟一个哈希表。”
如果我们可以设计一个完美的散列函数,则可以在 O(1) 时间内查找。底层数据结构仍然是一个数组。但这取决于要存储的值,我们是否可以设计一个完美的哈希函数。例如考虑到英文字母的字符串。由于没有已知的散列函数可以将每个有效的英文单词映射到唯一的 int(32 位)(或 long long int 64 位)值,所以总会有一些冲突。为了处理冲突,我们可以使用单独的链式冲突处理方法,其中每个哈希表槽都存储一个指向链表的指针,链表实际上存储了指向该特定槽或索引的所有项目哈希。例如考虑一个哈希函数,它将每个英文字母字符串视为以 26 为底的数字(因为英文字母有 26 个字符),这可以编码为:
unsigned int hash(const std::string& word)
{
std::transform(word.begin(), word.end(), word.begin(), ::tolower);
unsigned int key=0;
for(int i=0;i<word.length();++i)
{
key = (key<<4) + (key<<3)+(key<<2) + word[i];
key = key% tableSize;
}
return key;
}
Where tableSize is an appropriately chosen prime number just greater than the total number of English dictionary words targeted to be stored in the hash table.
以下是大小为 144554 的字典和大小为 144563 的表的结果:
[映射到同一单元格的项目 --> 哈希表中此类槽的数量] =======>
[ 0 --> 53278 ]
[1 --> 52962 ]
[2 --> 26833 ]
[3 --> 8653 ]
[4 --> 2313 ]
[5 --> 437 ]
[6 --> 78 ]
[7 --> 9 ]
在这种情况下,要搜索已映射到仅包含一个项目的单元格的项目,查找将是 O(1),但如果它映射到具有超过 1 个项目的单元格,则我们必须迭代通过这个可能包含 2 到 7 个节点的链表,我们将能够找到该元素。所以在这种情况下它不是恒定的。
所以它仅取决于完美哈希函数的可用性,我们是否可以在 O(1) 约束中执行查找。否则,它不会完全是 O(1),而是非常接近它。