线性探测具有不等散列的大量键序列
Linear probing huge sequences of keys with unequal hash
关于线性探测(哈希表)的一件事对我来说并不直观。
如果我将哈希结果的 key1 放入数组索引 1。然后我将 key2 -> 数组索引 2。然后我将 key3 -> 再次放入数组索引 1,这将转到数组索引 3。
然后,当我搜索 key3 时,我应该遍历包含与我的哈希值完全不同的键的索引。这不是浪费吗?如果序列真的很大并且包含很多键(例如,我有 20 个元素,然后为 null,对于导致数组索引从 0 到 20 的任何键,我必须遍历所有索引,尽管它们与我的哈希值不同我可以通过单独的链接来消除它)。
或者我们的散列函数(如果写得足够好)在索引之间平均分配键并且我们不断调整数组的大小以使其最大半满这一事实可以缓解这种情况?
当有很多碰撞时,线性探测不是最优的。请注意,冲突次数不仅取决于散列,还取决于 table 中的槽数(通常是质数),因为索引是散列除以整数的余数table长度。
但是请注意,将冲突的键一个接一个地排列也可能会利用 CPU 缓存,这将在一次读取中从 RAM 中获取许多元素。所以,不要(原则上)认为检查 20 个探测所需的时间是检查一个所需时间的 20 倍,因为 CPU 及其缓存内部发生的事情比进入 RAM 快得多。虽然没有魔法。如果每次比较的计算都丢弃了缓存中的内容,那么部分节省将丢失。
您确定的问题确实会影响线性探测的性能。当您尝试查找某个元素时,您可能必须从初始散列探测开始的地方看得很远才能找到您的元素。
也就是说,线性探测在实践中非常快,这主要是由于参考位置。在内存中查找内容的成本并不统一——如果您在最近阅读过的内容附近查找地址,很可能内存区域已被拉入缓存,查找内容的成本极低。因此,实际中这些探测的成本通常低于您自然预期的成本,因为这些探测可能非常快。
但是,这并不意味着您可以忽略这个事实。有许多问题需要注意。首先,随着 table 的负载因子增加,会出现一个点,即命中其他元素的成本开始使查找时间越来越长。通常,您会看到人们在大约 75% 的负载率下重新哈希到更大的 table。其次,你需要有一个非常好的散列函数,因为如果你有一个低质量的散列将大量元素放入相似的位置,你会因为你提到的原因而获得非常糟糕的性能。
您可以使用多种技术来缓解这种情况。 Robin Hood 散列法的工作原理是在元素被放置后四处移动,以便将离家较近的元素推得更远,从而为离家较近的元素腾出空间。这使得查找的平均成本有点高,但大大降低了查找的最坏情况成本(换句话说,它减少了查找成本的方差,以换取增加该查找成本的预期值)。 Hopscotch 哈希通过限制元素可以移动的最大距离并维护一个位掩码来指示附近的哪些元素可能匹配,从而减少查找内容所需的工作量。新的 Google flat_map
从线性探测开始,使用非常聪明的散列和并行内存操作使查找速度极快。
关于线性探测(哈希表)的一件事对我来说并不直观。 如果我将哈希结果的 key1 放入数组索引 1。然后我将 key2 -> 数组索引 2。然后我将 key3 -> 再次放入数组索引 1,这将转到数组索引 3。 然后,当我搜索 key3 时,我应该遍历包含与我的哈希值完全不同的键的索引。这不是浪费吗?如果序列真的很大并且包含很多键(例如,我有 20 个元素,然后为 null,对于导致数组索引从 0 到 20 的任何键,我必须遍历所有索引,尽管它们与我的哈希值不同我可以通过单独的链接来消除它)。
或者我们的散列函数(如果写得足够好)在索引之间平均分配键并且我们不断调整数组的大小以使其最大半满这一事实可以缓解这种情况?
当有很多碰撞时,线性探测不是最优的。请注意,冲突次数不仅取决于散列,还取决于 table 中的槽数(通常是质数),因为索引是散列除以整数的余数table长度。
但是请注意,将冲突的键一个接一个地排列也可能会利用 CPU 缓存,这将在一次读取中从 RAM 中获取许多元素。所以,不要(原则上)认为检查 20 个探测所需的时间是检查一个所需时间的 20 倍,因为 CPU 及其缓存内部发生的事情比进入 RAM 快得多。虽然没有魔法。如果每次比较的计算都丢弃了缓存中的内容,那么部分节省将丢失。
您确定的问题确实会影响线性探测的性能。当您尝试查找某个元素时,您可能必须从初始散列探测开始的地方看得很远才能找到您的元素。
也就是说,线性探测在实践中非常快,这主要是由于参考位置。在内存中查找内容的成本并不统一——如果您在最近阅读过的内容附近查找地址,很可能内存区域已被拉入缓存,查找内容的成本极低。因此,实际中这些探测的成本通常低于您自然预期的成本,因为这些探测可能非常快。
但是,这并不意味着您可以忽略这个事实。有许多问题需要注意。首先,随着 table 的负载因子增加,会出现一个点,即命中其他元素的成本开始使查找时间越来越长。通常,您会看到人们在大约 75% 的负载率下重新哈希到更大的 table。其次,你需要有一个非常好的散列函数,因为如果你有一个低质量的散列将大量元素放入相似的位置,你会因为你提到的原因而获得非常糟糕的性能。
您可以使用多种技术来缓解这种情况。 Robin Hood 散列法的工作原理是在元素被放置后四处移动,以便将离家较近的元素推得更远,从而为离家较近的元素腾出空间。这使得查找的平均成本有点高,但大大降低了查找的最坏情况成本(换句话说,它减少了查找成本的方差,以换取增加该查找成本的预期值)。 Hopscotch 哈希通过限制元素可以移动的最大距离并维护一个位掩码来指示附近的哪些元素可能匹配,从而减少查找内容所需的工作量。新的 Google flat_map
从线性探测开始,使用非常聪明的散列和并行内存操作使查找速度极快。