【问题标题】:Handling hash collisions when using linear probing使用线性探测时处理哈希冲突
【发布时间】:2023-04-04 05:58:01
【问题描述】:

我已阅读有关哈希表和开放地址的信息。如果要在大小为 13 的哈希表中插入键:18,32,44:

18 gets index 5 (18 modulus 13 = 5)
32 gets index 6 (32 modulus 13 = 6)
44 gets index 5 (44 modulus 13 = 5)

你会遇到冲突,因为索引 5 上已经有东西了。

如果您使用线性探测,您将执行 hashfunction = (key+i) modulus N where i = 0,1,2.. 直到您在哈希表中找到一个空白位置。然后 44 将被插入到索引 7 处。

如果你删除 32,然后你想删除 44。你首先查看hashfunction(44)=5 - 那不是 44,然后hashfunction(44 + 1) = 6 - 那是空的。然后你可能会认为 44 已经消失了。如何在哈希表中标记一个位置,该位置不是真正的空,但不包含键,并且您应该在下一个索引处继续寻找 44?

如果您随后需要在索引 6 处插入另一个键,那么该键只会覆盖哈希表中的“标记”。

你可以用什么来标记索引 - 说这里是一个键,但已被删除 - 所以你继续查看下一个索引?您不能只写 null 或 0,因为您认为键已被删除 (null) 或值为 0 的键已覆盖 44。

【问题讨论】:

    标签: java hashtable


    【解决方案1】:

    使用开放寻址处理哈希表的一种方法是使用状态标记:EMPTYOCCUPIEDDELETED。请注意,EMPTYDELETED 之间有一个重要区别,表示该位置从未使用过,但已被删除。

    当一个值被删除时,槽被标记为DELETED,而不是EMPTY。当您尝试检索一个值时,您将进行探测,直到找到一个标记为EMPTY 的槽;例如:您认为DELETED 插槽与OCCUPIED 相同。请注意,插入可以忽略这种区别 - 您可以插入到 DELETEDEMPTY 插槽中。

    这个问题被标记为 Java,这有点误导,因为 Java(或者至少是 Oracle 的实现)不使用开放寻址。当负载因子变高时,开放寻址会出现特别问题,这会导致哈希冲突更频繁地发生:

    如您所见,在 0.7 大关附近出现了显着的性能下降。一旦负载因子超过某个常数因子,大多数哈希表就会调整大小。例如,当负载因子超过 0.75 时,Java 会将其 HashMap 的大小翻倍。

    【讨论】:

    • 谢谢 :) 我可以使用前枚举吗?因为我的 hashtable 包含整数.... 那么如何在整数数组中实现 EMPTY,DELETED,OCCUPIED 呢?
    • @Anne 我会使用一个单独的数据结构(例如:一个数组)来跟踪这些状态。
    • 我倾向于实现线性探测,每个桶上的 Next 字段指向下一个碰撞。当我删除一个碰撞项目时,我只需更新前一个项目的 Next 引用并将删除的存储桶添加到空闲存储桶列表中。
    【解决方案2】:

    您似乎正在尝试实现自己的哈希表(与使用 java 中包含的 Hashtable 或 HashMap 相比),所以它更像是一个数据结构问题而不是 java 问题。

    话虽如此,在删除元素时,使用开放寻址(例如线性探测)实现哈希表并不是很有效。正常的解决方案是“拉起”所有位于错误插槽中的元素,这样探测中就不会出现任何空格。

    维基百科上有一些伪代码很好地描述了这一点:

    http://en.wikipedia.org/wiki/Open_addressing

    【讨论】:

    • 谢谢 :) 对 java 感到抱歉
    【解决方案3】:

    哈希表存储桶不限于存储单个值。因此,如果两个对象散列到表中的同一位置,它们都将被存储。冲突仅意味着查找会稍微慢一些,因为在使用散列到特定位置的键查找值时,它需要检查每个条目以查看它是否匹配

    听起来您正在描述一个仅存储单个条目和每个索引的哈希表。我能想到的唯一方法是向结构中添加一个字段,该字段存储指示该位置是否发生碰撞的值。然后在进行查找时,您将检查键,如果它匹配,则您有值。如果没有,那么您将检查是否存在碰撞,然后检查下一个位置。在删除时,您必须保留碰撞标记,但删除值和键。

    【讨论】:

    • 我知道您可以使用哈希表,其中每个索引处可以有多个值。但就我而言,我尽量不这样做。我只想要每个索引一个值。我不需要标记发生了冲突——我只是使用线性探测重新验证一个新索引。但是在删除时-我如何将索引标记为“有一个值但现在已删除,请尝试下一个索引以查看您关注的数字是否是那个”-我可以使用 ex -1 来标记该位置吗?但是 -1 也不能是键吗?
    • @Anne:冲突由 HashMap 为您处理。 HashMap 在每个桶中存储了一个对象列表。如果哈希码很好,则每个桶中只有一个元素。如果不是,那么有些桶有几个键。最后,equals 用于比较键。 hashCode 仅用于查找存储桶。
    • 但如果我不想使用哈希表,每个索引处可以有更多对象。我的问题不是碰撞而是删除。删除后遍历哈希表时如何标记之前有值。
    • 为了简化我的问题 - 如何标记某个索引已在该索引处但现在已删除 - 我不能使用 null。
    • @Anne 您的描述在功能上等同于哈希表的工作方式,您只是尝试在其他索引处分发条目,而不是在每个索引处管理条目列表。无论如何,我看不出“标记发生冲突”与存储“-1”以标记删除值的位置之间的区别。
    【解决方案4】:

    如果您使用使用这种方法的哈希表(内置哈希集合都没有这样做),您需要遍历所有后面的键以查看它们是否需要向上移动(以避免漏洞)。有些可能是相同的哈希值,有些可能是不相关的哈希码的冲突。如果你这样做,你就不会留下任何漏洞。对于不太满的哈希映射,这不会产生太多开销。

    【讨论】:

    • 但是如果我不想在删除时移动所有键,你知道在索引获取之前我可以使用哪个标记(哈希表认为不是键的符号或整数)被新密钥覆盖。如果我使用 ex -1 作为标记,则哈希表认为它是具有值的键。不是吗?
    • 这种方法的问题在于,当您进行查找时,您需要检查每个标记和未标记的键(即,即使地图为空,也可能是每个键)一个空条目。
    猜你喜欢
    • 2013-03-25
    • 1970-01-01
    • 2017-08-30
    • 1970-01-01
    • 2013-09-01
    • 2015-04-07
    • 1970-01-01
    • 1970-01-01
    • 2022-07-17
    相关资源
    最近更新 更多