繁体   English   中英

设计一个Hashtable

[英]Design a Hashtable

我在面试中被问到这个问题并且被遗忘了,尽管我想出了一个答案我对我的解决方案感到不舒服。 我想看看这里的专家对这个问题的看法。

我正在引用面试官的问题。 “设计一个哈希表,你可以使用你想要的任何数据结构。我想看看你如何实现O(1)查找时间”。 最后他说,这更像是通过另一个数据结构模拟哈希表。

有关这个问题的更多信息,任何人都可以点亮我。 谢谢!

PS:我提出这个问题的主要原因是要知道专家设计师如何从这个问题的设计开始&&还有一件事,我根据提出的其他问题以某种方式清除了采访,但这个问题在我脑海中,我想找出答案!

这是一个相当标准的面试问题,它表明你理解了基础概念是有用的Java数据结构,比如HashSetHashMap

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

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

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

随着表格越来越丰富,您最终会进行越来越多的线性搜索,因此您最终需要重新哈希。 这意味着构建一个全新的,更大的表并再次将对象放入其中。

如果您想要的那个位置已满,则可以重新计算不同的铲斗位置,而不是在每个阵列位置使用List,常见的方法是二次探测 这样做的优点是不需要任何动态数据结构,如列表,但更复杂。

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

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

如果我愿意,我应该做到以下几点:

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

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

Set S的大小合理S⊆U。因此,这套S的大小合理,就像你保留朋友的电话号码一样。

选择实现数据结构没有数据结构,我们将无法获得良好的解决方案。 我们可以使用数组进行快速插入,删除和查找,但由于Universe的大小非常大,它会占用大量空间。 此外,您的朋友名称应为整数,空间要求与Universe成正比。

另一方面,我们可以使用链表。 这只会占用空间,因为有对象即Set S,但3次操作不会是O(1)。 要解决这个问题,我们可以使用两者。

因此,解决方案是使用两全其美,即快速查找数组和小型存储大小,如链接列表。

但是,这些真实世界实体需要通过称为哈希函数的东西更改为整数,以便它们可以用作数组索引。 所以,假设您想保存朋友的名字alice,只需将他的名字转换为整数即可

插入爱丽丝:
int k = hashFunc(alice);
arr[k] = Alice //this takes O(1) time

查找alice:
int k = hashFunc(alice);
string name = arr[k] ;
print name;//prints alice

当然,这并不是那么简单,但这是我现在可以解释的。 如果我不清楚,请告诉我。谢谢。 有关哈希表的更多信息,请参阅此处

哈希表提供了一种有效插入和检索数据的方法(通常在常量/ O(1))时间。 为此,我们使用一个非常大的数组来存储目标值和一个通常映射目标值的哈希函数,这些哈希值只是这个大数组中的有效索引。 完美地散列值以存储到唯一键(或表中的索引)的散列函数被称为完美散列函数。 但实际上,为了存储没有已知方法来获取唯一哈希值(表中的索引)的值,我们通常使用哈希函数,该函数可以将每个值映射到特定索引,以便可以将冲突保持为最小。 这里碰撞意味着要存储在散列表中的两个或更多个项映射到相同的散列值。

现在来看原始问题,即:“设计一个哈希表,你可以使用你想要的任何数据结构。我想看看你如何实现O(1)查找时间”。 最后他说,这更像是通过另一个数据结构模拟哈希表。“

如果我们可以设计一个完美的哈希函数,那么在O(1)时间内查找是可能的。 底层数据结构仍然是一个数组。 但它取决于要存储的值,我们是否可以设计完美的哈希函数。 例如,将字符串视为英文字母。 由于没有已知的散列函数可以将每个有效的英语单词映射到唯一的int(32位)(或long long int 64 bit)值,因此总会有一些冲突。 为了处理冲突,我们可以使用冲突处理的单独链接方法,其中每个哈希表槽存储指向链表的指针,该链表实际上存储了对该特定槽或索引的所有项哈希。 例如,考虑一个哈希函数,它将每个英文字母字符串视为基数为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;
}

其中tableSize是适当选择的素数,其大于目标存储在哈希表中的英语词典单词的总数。

以下是字典大小为144554的结果,以及大小= 144563的表:

[映射到相同单元格的项目 - >哈希表中此类插槽的数量] =======>

[ 0  -->   53278 ]
[1 --> 52962 ]
[2 --> 26833 ]
[3 --> 8653  ]
[4 --> 2313 ]
[5 --> 437 ]
[6  --> 78 ]
[7  -->  9 ]

在这种情况下,要搜索已映射到仅包含一个项目的单元格的项目,查找将为O(1),但如果它映射到具有多个项目的单元格,则我们必须遍历此链接列表可能包含2到7个节点,然后我们将能够找到该元素。 所以在这种情况下它不是常数。

所以它只取决于完美散列函数的可用性,我们是否可以在O(1)约束中执行查找。 否则它不会完全是O(1)但非常接近它。

使用数组=> O(1)

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM