繁体   English   中英

当hashcode()实现返回常量值时,为什么哈希表会退化为链表?

[英]Why does a hashtable degenerate into a Linked List when a hashcode() implementation returns a constant value?

// The worst possible legal hash function - never use!
@Override public int hashCode() { return 42; }

这是合法的,因为它确保了相等的对象具有相同的哈希码。 这很糟糕,因为它确保每个对象都具有相同的哈希码。 因此,每个对象都会散列到同一个存储桶,并且散列表会退化为链接列表。 应该以线性时间运行的程序改为以二次方运行。

我试图弄清楚如何(引自第47页,第9项,Joshua Bloch的Effective Java)。

我看到它的方式如下(考虑以下代码):

Map<String, String> h = new HashMap<String,String>();
h.put("key1", "value1");
h.put("key1", "value2");

第二个h.put("key1",...)命令发生的情况如下:1。获取key1的哈希码2.获取代表上述哈希码的桶3.在该桶中,为每个对象调用equals方法,用于查找是否存在相同的对象。

这有点快,因为首先你找到对象的“组”(桶),然后找到实际的对象。

现在,当hashcode实现为ALL对象返回相同的整数(例如42以上)时,只有一个桶,并且需要在整个对象上逐个调用equals方法HashMap中/哈希表。 这与链表一样糟糕,因为如果链表中的对象也是如此,则必须逐个比较(调用equals)每个对象。

有人说,这就是哈希表退化为链表的原因吗?

(我为上述文本的冗长而道歉。我的概念中我不够清楚地说明它更简洁)

是的,你的理解似乎是准确的。 但是,它不像链接列表。 共享一个公共存储桶的条目的实际内部实现一个普通的旧链表。 存储桶将Map.Entry保存在列表的开头,每个条目都有一个指向其存储桶下一个占用者的前向指针。 (当然,为了实现内置于Java中的HashMap。)

HashTable是一个具有映射功能(hashCode)的数组。 插入数组时,您可以计算位置并在此处插入元素。

但是,hashCode不保证每个元素都有不同的位置,因此一些对象可能会发生碰撞(具有相同的地址),而hashTable必须解决它。 有两种常见的方法,如何做到这一点。

单独链接

在单独的链接(在Java中使用)中,数组的每个索引都包含一个链表 - 因此每个存储桶(位置)都具有无限容量。 因此,如果你的hashCode只返回一个值,你只使用一个like list => hashTable是一个链表。

线性探测

第二种方法是线性探测。 在线性探测中,内部数组实际上是正常的元素数组。 当您发现该位置已被占用时,您将迭代数组并将新元素放在第一个空位置。

所以我你的hashCode的impl为每个元素生成了一个含有的值,你只生成了colisions,因此你试图将所有元素放在同一个索引上,因为它总是被占用,你迭代所有放置的元素并放置新元素在this structure的最后。 如果你再读一遍,你在做什么,你必须看到,你只使用链表的另一个(你可以说是隐含的)实现。

为什么不这样做

你真的不应该返回常量值,因为哈希表是为了提供O(1)预期的搜索和插入操作的复杂性而构建的(因为哈希函数为(几乎)每个不同的对象返回一个不同的地址)。 如果只返回一个值,则对于两个操作,实现都会降级为链接列表,其中包含O(n)

哈希表 - 如果使用正确 - 平均提供常量时间查找。 就时间复杂性而言,恒定时间和它一样好。

链接列表提供线性时间查找。 线性时间(即依次查看每个元素)和它一样糟糕。

当哈希表以Bloch描述的方式被滥用时,其查找行为退化为链表的行为,仅仅因为它实际上变成了链表。

关于其他操作可以说类似的事情。

暂无
暂无

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

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