我最近学习了有关哈希表中处理冲突的不同方法,并发现使用带有链表的分离链接法总是比线性探测法更高效。对于空间效率,我们为线性探测法分配预定义的内存,但后来可能不会使用,而对于分离链接法,我们动态使用内存。
使用带有链表的分离链接法比线性探测法更有效吗?如果是这样,那么为什么我们还要使用线性探测法呢?
我最近学习了有关哈希表中处理冲突的不同方法,并发现使用带有链表的分离链接法总是比线性探测法更高效。对于空间效率,我们为线性探测法分配预定义的内存,但后来可能不会使用,而对于分离链接法,我们动态使用内存。
使用带有链表的分离链接法比线性探测法更有效吗?如果是这样,那么为什么我们还要使用线性探测法呢?
我很惊讶你认为链式哈希比线性探测更快——在实践中,线性探测通常比链式哈希快得多。这主要是由于参考局部性,因为线性探测中执行的访问通常比链式哈希中执行的访问更接近于内存。
线性探测还有其他优势。例如,在线性探测哈希表中插入不需要进行任何新的分配(除非您重新对表进行哈希),因此在像网络路由器这样内存稀缺的应用中,一旦建立了表,就可以将元素放入其中,而不必担心malloc
失败的风险。
线性探测的一个弱点是,如果散列函数选择不当,一次聚集会导致表的性能显著下降。虽然链式哈希仍然可能受到糟糕的散列函数的影响,但它对具有相邻哈希码的元素不太敏感,从而不会对运行时产生不良影响。理论上,只有当哈希函数是5独立的或者键中具有足够的熵时,线性探测才能给出期望的O(1)查找。有许多解决方法,例如使用Robin Hood哈希技术或跳跃式哈希,这两种技术的最坏情况明显比普通的线性探测好得多。
线性探测的另一个弱点是,在负载因子接近1时,其性能会显著下降。您可以通过定期重新哈希或使用上述描述的Robin Hood哈希技术来解决这个问题。
希望这能有所帮助!当哈希表接近满时,线性探测实际上更具有内存效率。
历史上,人们拥有非常非常少的内存,所以每个字节都很重要(现在仍有一些内存非常有限的情况)。
为什么它使用的内存更少?
考虑表的外观:(根据 维基百科 的分离链接变体 - 还有其他变体,但它们通常使用更多的内存)
Linear Separate chaining #1 Separate chaining #2
probing List head in table Pointer in table
|------| |------|---| |---| |------|---|
|Object| |Object|Ptr| |Ptr| -> |Object|Ptr|
|------| |------|---| |---| |------|---|
|Object| |Object|Ptr| |Ptr| -> |Object|Ptr|
|------| |------|---| |---| |------|---|
| NULL | | NULL |Ptr| |Ptr|
|------| |------|---| |---|
. . .
. . .
. . .
(Ptr
代表 "指针" - 任何未指向任何东西的指针都可以被视为 NULL
)
与线性探测相比,分离链接 #1 明显使用更多内存(始终如此),因为表中的每个元素都比指针的大小大。
当表中没有太多内容时,分离链接 #2 可能具有优势,但当其被填满时,它将在每个元素周围大约有另外2个指针。
templatetypedef 关于线性探测通常更快(他很少错)可能是正确的,但通常教授分离链接更快,并且您可以在主要 API 中看到它(例如像Java 实现),也许是因为这种信仰,以避免线性探测明显较慢的情况(通过选定一些值,您可以快速地达到 O(n)
性能,而分离链接仍将是 O(1)
),或者出于其他原因。