我通过这本python书教自己数据结构,如果我错了,我会感激,如果我错了,因为哈希集看起来与哈希映射非常相似。 / p>
实施 Hashset是list []或数组,其中每个索引指向链表的头部
所以一些哈希(some_item) - >键,然后列出[键],然后添加到LinkedList的头部。这发生在O(1)时间
当从链表中删除一个值时,在python中我们用占位符替换它,因为不允许哈希值具有Null / None值,对吗?
当列表[]超过某个%的加载/充满度时,我们将其复制到另一个列表
关于时间复杂性混淆: 所以有一个问题是,为什么平均搜索/访问O(1)是否可以在给定索引处的链表中列出N个项目?
搜索项目的平均情况不在其索引链表的中间,所以它应该是O(n / 2) - >为O(n)吗
此外,在删除项目时,如果我们用占位符值替换它,如果从不使用占位符,这是否会浪费内存?
最后,除了HashMaps之外,这个和HashMap有什么区别可以有空值?而HashMaps是关键/值,而Hashsets只是值?
答案 0 :(得分:0)
对于您的第一个问题 - 为什么查找O(1)的平均时间复杂度? - 如果你有一个好的哈希函数,这个语句一般都是正确的。理想的哈希函数是在其元素上产生良好传播的函数。特别地,通常选择散列函数,使得任何两个元素碰撞的概率很低。在这种假设下,可以正式证明要检查的元素的预期数量是O(1)。如果你在网上搜索“通用的哈希函数族”,你可能会发现这个结果的一些很好的证明。
至于使用占位符 - 有几种不同的方法来实现哈希表。您正在使用的方法称为“封闭寻址”或“带链接的散列”,在这种方法中,没有理由使用占位符。但是,也存在其他散列策略。一种常见的方法系列称为“开放寻址”(其中最着名的是线性探测散列),在这些设置中,占位符元素是避免错误否定查找所必需的。在线搜索有关这方面的更多详细信息可能会为您提供有关原因的详细解释。
至于它与HashMap的区别,HashMap只是哈希表支持的地图抽象的一种可能实现。 Java的HashMap确实支持空值,而其他方法则不支持。
答案 1 :(得分:0)
查找时间不会是O(n)
,因为不是所有项都需要搜索,它还取决于存储桶的数量。更多的桶会降低碰撞的可能性并减少链长。
通过根据需要调整哈希表的大小,可以将桶的数量保持为条目数的常数因子。除了均匀分布值的哈希函数外,这还可以保持预期的链长有限,从而提供恒定的时间查找。
hashmaps和hashsets使用的哈希表是相同的,除了它们存储不同的值。散列集将包含对单个值的引用,而散列映射将包含对键和值的引用。可以通过委托密钥和值相同的哈希映射来实现哈希集。
答案 2 :(得分:0)
这里已经写了很多关于开放哈希表的内容,但是遗漏了一些基本点。
实际实现通常具有O(1)查找和删除,因为它们保证存储桶不会包含超过固定数量的项(加载因子)。但这意味着它们只能实现摊销 O(1)插入时间,因为表需要在增长时定期重组。
(有些人可能会选择重新组织删除,同时,当加载因子达到某个最低阈值时收缩表,这只会影响空间,而不是渐近的运行时间。)
重组意味着增加(或减少)存储桶数量并将所有元素重新分配到新存储桶位置。有一些方案,例如可扩展散列,使其更便宜一些。但总的来说,这意味着触摸表格中的每个元素。
然后,重组是O(n)。如果任何给定的人可能会产生这笔费用,如何插入O(1)?秘密是摊销和权力。当表格增长时,必须增长因子大于一,两个是最常见的。如果表以1个桶开始,并且每次加载因子达到F时加倍,那么N个重组的成本是
F + 2F + 4F + 8F ... (2^(N-1))F = (2^N - 1)F
此时该表包含(2^(N-1))F
个元素,即上次重组期间表中的数字。即我们已经完成(2^(N-1))F
次插入,重组的总成本如右图所示。有趣的部分是表中每个元素的平均成本(或插入,选择):
(2^N - 1)F 2^N
---------- ~= ------- = 2
(2^(N-1))F 2^(N-1)
那是分摊的O(1)来自。
另外一点是,对于现代处理器,链表不是桶列表的好主意。使用8字节指针,开销很有意义。更重要的是,单个列表中的堆分配节点在内存中几乎不会是连续的。遍历这样的列表会导致缓存性能下降,这会降低数量级的速度。
数组(包含数据元素数量的整数计数)可能会更好。如果负载系数足够小,只需在第一个元素插入存储桶时分配一个大小相等的数组。否则,按与桶数组相同的方式增长这些元素数组!一切仍将摊还到O(1)。
要从此类存储桶中删除项目,请勿将其标记为已删除。只需将最后一个数组元素复制到已删除的数组元素的位置,然后减少元素数。当然,如果允许外部指针进入散列桶,这将不起作用,但无论如何这都是一个坏主意。