如何有效地为 Java 中的单链表节点实现 hashCode()?
Posted
技术标签:
【中文标题】如何有效地为 Java 中的单链表节点实现 hashCode()?【英文标题】:How to efficiently implement hashCode() for a singly linked list node in Java? 【发布时间】:2014-05-28 13:52:47 【问题描述】:Eclipse 通过以下方式为单链表的 Node 类实现 hashCode()
函数:
class Node
int val;
Node next;
public Node(int val)
this.val = val;
next = null;
@Override
public int hashCode()
final int prime = 31;
int result = 1;
result = prime * result + ((next == null) ? 0 : next.hashCode());
result = prime * result + val;
return result;
现在,一个节点的hashCode()
取决于它后面的节点的哈希码。
因此,hashCode()
的每次调用都将在链表的长度上花费线性时间。因此使用HashSet<Node>
将变得不可行。
解决此问题的一种方法是将hashCode
的值缓存在一个变量中(称为散列),以便只计算一次。但即使在这种情况下,一旦任何节点的 val 更改,哈希也会变得无效。同样,修改当前节点之后的节点hashCode
需要线性时间。
那么对于这样的链表节点有什么好的实现散列的方法呢?
【问题讨论】:
好吧,只是return val;
,因为它是int
;不过,这取决于您如何实现.equals()
问题是节点代表整个列表,因此它的哈希码取决于整个列表。
这样想——如果你不说ll1.equals(ll2)
是true
,那么你肯定不希望他们的hashCode()
s是一样的。因此,以尊重该合同的方式实施它。
@NikunjBanka 以下两个链表是否“相等”? [x] -> [x] -> NULL
与 [x] -> [x] -> [x] -> NULL
;它们只包含x
,但其中一个更长。考虑一下。
致对这个问题投反对票的人:这是一个完全有效的问题,值得思考。绝对不值得一票。
【参考方案1】:
阅读您的问题后,我的第一个想法是:LinkedList
是做什么的?深入研究源代码,我们发现内部LinkedList.Node
类(link to source)上没有定义hashCode()
或equals()
。
为什么这有意义?嗯,节点通常是内部数据结构,只对列表本身可见。它们不会被放入需要比较相等性和哈希码的集合或任何其他数据结构中。没有外部代码可以访问它们。
你在你的问题中说:
因此使用
HashSet<Node>
将变得不可行。
但我认为您无需将节点放置在这样的数据结构中。根据定义,您的节点将相互链接,并且不需要额外的类来促进这种关系。除非您打算将此类公开在您的列表之外(这不是必需的),否则它们将永远不会出现在 HashSet
中。
我建议您遵循LinkedList.Node
模型并避免在您的节点上创建这些方法。外部列表可以将其哈希码和相等性基于存储在节点中的值(但不是节点本身),LinkedList
就是这样做的 - 请参阅 AbstractList
(link to source)。
源链接指向 OpenJDK 源,但在这种情况下,它们与 Oracle JDK 提供的源相同
【讨论】:
【参考方案2】:您必须问自己,什么样的哈希质量对您来说是有价值的。唯一的限制是确保以相同顺序具有相同编号的另一个列表具有相同的哈希值。这是通过使用连续数字以及使用第一个数字以及限制 5 个数字来实现的。多少数字对您有意义取决于您的数据结构。例如,如果您总是存储从 1 开始的连续升序数字,而区别只是长度,那将很难优化。如果它在整个 int 范围内完全随机,则第一个数字将做得很好。有多少数字可以为您提供最佳比率,我会说是通过测量得出的。
最后,您需要的是碰撞(将对象放入同一个桶)和计算时间之间的良好比例。生成的实现通常会尝试最大化计算时间,为人类开发人员提供很大的改进空间。 ;-)
关于包含值的更改:java.util.HashSet(分别是它所拥有的 HashMap)将根据您的哈希计算自己的哈希,并将其缓存。因此,如果包含在 HashSet 中的对象一旦更改到其哈希更改的程度就无法再次找到。
【讨论】:
以上是关于如何有效地为 Java 中的单链表节点实现 hashCode()?的主要内容,如果未能解决你的问题,请参考以下文章