设计一个哈希表

Posted

技术标签:

【中文标题】设计一个哈希表【英文标题】:Design a Hashtable 【发布时间】:2011-07-21 09:57:35 【问题描述】:

我在一次采访中被问到这个问题并被难住了,即使我想出了一个答案,但我对我的解决方案并不满意。我想看看这里的专家对这个问题的看法。

我完全引用了采访者提出的问题。 “,你可以使用任何你想要的数据结构。我想看看你如何实现 O(1) 查找时间”。最后他说这更像是通过另一个数据结构来模拟一个哈希表。

谁能告诉我关于这个问题的更多信息。谢谢!

PS:我提出这个问题的主要原因是想知道专家设计师将如何开始针对这个问题的设计 && 还有一件事我根据其他被问到的问题以某种方式通过了面试,但这个问题在我的想法,我想找出答案!

【问题讨论】:

你应该给我们你的答案,至少直到你觉得自己迷失的程度。说“这就是我所知道的”并不尴尬。首先描述什么是哈希表。 了解哈希表是否设计为可变的相关。如果您预先准备好所有数据,那么您可以更好地设计它。 这让我想知道问题是否出现了,因为 OP 声称哈希表总是有 O(1) 查找时间。 @Bart Kiers,@delnan,@Blindy 我说在我搞砸了链接列表方法后我不知道如何继续!! @user645466,除了“我对我的解决方案感到不舒服”之外,你没有提到任何关于你的尝试的内容。 【参考方案1】:

这是一个相当标准的面试问题,表明您了解有用的 Java 数据结构的基本概念,例如 HashSets 和 HashMaps。

您将使用一组列表,这些列表通常称为 buckets。您以给定的容量 n 开始哈希表,这意味着您有一个包含 10 个列表的数组(全部为空)。

要将对象添加到您的 hastable 中,您可以调用 objects hashCode 函数,它会为您提供一个 int(一个很大范围内的数字)。因此,您必须将 hashCode wrt 取模为 n 以提供它所在的存储桶。将对象添加到该存储桶中列表的末尾。

要查找对象,您再次使用 hashCode 和 mod 函数查找存储桶,然后需要使用 .equals() 遍历列表以找到正确的对象。

随着表越来越满,您最终会进行越来越多的线性搜索,因此您最终需要重新散列。这意味着构建一个全新的、更大的桌子,然后再次将对象放入其中。

如果您想要的存储桶已满,您可以重新计算不同的存储桶位置,而不是在每个数组位置使用列表,常用方法是quadratic probing。这样做的好处是不需要任何动态数据结构,如列表,但更复杂。

【讨论】:

非常感谢!!这正是我想要的! 还有一个问题:你怎么知道我们在谈论Java? 我认为大脑所说的在这里部分匹配(即使它使用不同的碰撞技术)geeksforgeeks.org/…【参考方案2】:

您需要一个列表数组或“桶”来存储您的值。然后使用哈希函数确定要查找的数组元素,最后通过那里的列表元素进行线性搜索。

您可以不断查找数组位置,并在那里的小列表中线性搜索哈希值。

【讨论】:

您需要在每个数组元素中保存列表以处理哈希“冲突”,其中哈希函数为两个或多个元素返回相同的值。 还有其他方法(例如,CPython 的 dict 实现,只是通过固定模式移动到数组中的不同插槽),但是这个方法很好也很简单。【参考方案3】:

如果我能像你一样,我应该做以下事情:

讨论到底什么是哈希表以及应该在什么情况下使用它。 从消费者的角度讨论其中一种实现(例如它的 .net 框架实现)。 与面试官讨论“HashTable 如何在内部发挥作用”。这个非常重要。只有了解哈希表的工作原理,才能设计它。 破解难题:a.数据结构的选择 b.哈希函数的选择 使用TDD(Test Driven Development)设计和实现HashTable类。仅实现您被要求的功能。

【讨论】:

我很想看看 HashTable 类是如何从 TDD 实现的。第一次测试是什么样的?【参考方案4】:

使用数组 => O(1)

因此,您可以使用散列函数将键转换为数字,然后将该数字用作数组的索引以检索值。

【讨论】:

如果两个值具有相同的哈希值会怎样? @Space_C0wb0y:后者迷路了。这就是你为 O(1) 付出的代价 这是解决问题的一种方法。但是随机丢弃我插入的数据的数据结构对我来说似乎不是很有用。 在第一个对象的哈希码为 30,000 的情况下,这种方法会浪费大量内存。【参考方案5】:

考虑宇宙 U(例如,所有可能的 IP 地址,或所有可能的名称或所有可能的手机号码或所有可能的棋盘配置)。你可能已经注意到宇宙 U 非常大。

Set S 的大小是合理的 S⊆ U。所以,这个 Set S 的大小是合理的,就像你保留朋友的电话号码一样。

为实现选择数据结构 没有数据结构,我们不会得到好的解决方案。我们可以使用数组进行快速插入、删除和查找,但是会占用大量空间,因为 Universe 的大小非常大。此外,您的朋友姓名应该是整数,并且空间要求与宇宙成正比。

另一方面,我们可以使用链表。这只会占用与对象(即 Set S)一样多的空间,但 3 个操作不会是 O(1)。为了解决这个问题,我们可以同时使用。

因此,解决方案是使用两全其美的方法,即快速查找数组和像链接列表这样的小型存储空间。

但是,这些现实世界的实体需要通过一种称为散列函数的东西变为整数,以便它们可以用作数组索引。所以,假设你想保存你朋友的名字 alice,只需将他的名字转换为整数插入 alice: int k = hashFunc(alice); arr[k] = Alice //this takes O(1) time

查找爱丽丝: int k = hashFunc(alice); string name = arr[k] ; print name;//prints alice 当然不是那么简单,但这就是我现在可以解释的。请让我知道我不清楚的地方。谢谢。有关哈希表的更多信息,请参阅here

【讨论】:

【参考方案6】:

哈希表提供了一种有效插入和检索数据的方法(通常在常数/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),而是非常接近它。

【讨论】:

以上是关于设计一个哈希表的主要内容,如果未能解决你的问题,请参考以下文章

算法设计8——全域哈希表与完全哈希表

Leetcode刷题100天—706. 设计哈希映射(哈希表)—day74

Leetcode刷题100天—706. 设计哈希映射(哈希表)—day74

Leetcode刷题100天—706. 设计哈希映射(哈希表)—day74

数据结构 哈希表建立

设计一个哈希表