如何解决选择一个好的哈希表长度证明为什么是素数
我已经在 SO 和网络上阅读了几个关于选择一个好的哈希表长度的答案,它应该是减少冲突和在哈希表中均匀分布键的主要内容。
虽然答案很多,但我找不到一个令人满意的证明,我也没有理解我找到的解释。
因此,如果我们有一个键 k
和一个长度为 n
的哈希表,并且我们执行 k % n = i
以在哈希表中找到一个桶的索引 i
,我们说 n
应该是一个质数,以最小化冲突的数量并更好地在哈希表中分配键。
但是为什么呢?这是我试图证明这一点的尝试。它会很长,有点迂腐,但请耐心等待,尽量读到最后。
我将首先做出以下假设:
- 对于键集合
k
中的每个键K
,我们可以有一个k
,它可以是偶数或奇数。键是一个整数,可以是偶数 (k = 2x
) 或奇数 (k = 2x + 1
)。 - 对于我们可以选择的每个
n
,n
也可以是偶数 (n = 2y
) 或奇数 (n = 2y + 1
)。 - 如果我们将一个偶数与另一个偶数相加,我们会得到一个偶数 (
2x + 2y = 2(x + y)
)。同样,如果我们把一个奇数加到另一个奇数上,我们仍然得到一个偶数 ((2x + 1) + (2y + 1) = 2x + 1 + 2y + 1 = 2x + 2y + 2 = 2(x + y + 1)
)。 - 如果我们将奇数与偶数相加(与将偶数与奇数相加相同),我们总是得到奇数 (
(2x + 1) + 2y = 2x + 1 + 2y = 2(x + y) + 1
)。
首先,让我们试着考虑使用一个不是质数的n
,所以也许我们会发现这些数字不足以用作哈希表的长度(假设键共享某些模式,例如全部为偶数或全部为奇数)。
- 假设
n
是偶数,即n = 2y
。在这种情况下,我们有 2 种情况:k
的键K
可以是偶数 (1.1.) 或奇数 (1.2.)。
1.1. n = 2y
是偶数,键是偶数 k = 2x
对于 k = 2x
和 n = 2y
,我们有:k % n = 2x % 2y = i
。
在这种情况下,我们可以说如果键 k
和哈希表长度 n
都是偶数,
那么 i
也将永远是偶数。
为什么?因为如果我们用整数除法 k // n = 2x // 2y = q
取商,我们得到商 q
使得:
k = 2x = (n * q) + i = (2y * q) + i = 2yq + i
由于 2yq
(2y * q
) 是偶数,为了满足 2x = 2yq + i
,余数 i
总是会是偶数,因为 2x
是甚至 (even + even = even
)。如果 i
是奇数,我们会得到一个奇数 (even + odd = odd
),但 2x
也是偶数。
如果我们选择 n
为偶数,这会导致以下问题:如果我们所有的 k
都是偶数,那么它们将始终以偶数索引出现在存储桶中,从而增加数量冲突和聚类,因为只有我们哈希表长度的一半(只有偶数索引)会被占用。
因此,如果我们所有的 n / 2
或我们的大多数 n
都是偶数,那么为 k
使用偶数并不是一个好主意。
1.2. k
是偶数,键是奇数 n = 2y
对于 k = 2x + 1
和 k = 2x + 1
,我们有:n = 2y
。
同样,在这种情况下,如果我们所有的 k % n = (2x + 1) % 2y = i
(或其中大部分)都是奇数,我们最终会处于这种情况:
k
由于 k = 2x + 1 = (n * q) + i = (2y * q) + i = 2yq + i
是偶数,为了得到奇数 2yq
,k = 2x + 1
总是会是奇数 (i
)。
同样,即使我们的所有或大部分 even + odd = odd
都是奇数,选择偶数 n
作为哈希表长度也是一个坏主意,因为我们最终只会得到奇数索引(桶)被占用。
所以让我们尝试使用不是偶数的 k
,即奇数 n
。
- 假设
n = 2y + 1
是奇数,即n
。我们仍然有偶数 (2.1.) 和奇数 (2.2.) 键(n = 2y + 1
的k
)。
2.1. K
是奇数,键是偶数 n = 2y + 1
这里有:
k = 2x
我们知道 k = 2x = (n * q) + i = ((2y + 1) * q) + i = (2yq + q) + i = 2yq + q + i
是偶数,所以为了得到 2yq
也是偶数,我们需要 k = 2x
也是偶数。
q + i
什么时候可以是偶数?仅在这两种情况下:
-
q + i
、q -> even
、i -> even
-
even + even = even
、q -> odd
、i -> odd
如果 odd + odd = even
或 q
是偶数而另一个是奇数,我们将得到一个奇数 i
,因此得到一个奇数 q + i
,但我们有 { {1}} 是偶数,因此 2yq + (q + i)
和 k = 2x
都是偶数或都是奇数。
在这种情况下,我们可以看到对于奇数 q
,i
可以是偶数或奇数,这很好,因为这意味着现在我们将同时使用我们的偶数和奇数桶索引哈希表,而不仅仅是偶数或奇数。
顺便说一下,事实证明所有质数 n = 2y + 1
都是奇数,所以至少现在我们可以说选择一个质数可能是个好主意,因为一个质数更大比 2 总是奇数。
2.2. i
是奇数,键是奇数 p : p > 2
这里也一样:
n = 2y + 1
为了得到一个奇数 k = 2x + 1
,我们需要 k = 2x + 1 = (n * q) + i = ((2y + 1) * q) + i = 2yq + q + i = 2yq + (q + i)
为奇数(k = 2x + 1
为偶数),这仅在以下两种情况下发生:
-
(q + i)
、2yq
、q -> even
-
i -> odd
、even + odd = odd
、q -> odd
我们再次证明奇数是 i -> even
的更好选择,因为这样我们就有机会同时占用偶数和奇数桶的索引 odd + even = odd
。
现在,我被困在这里了。此证明与质数之间是否存在联系,我如何继续此证明以得出质数 n
比具有类似推理的通用奇数更好的选择?
编辑:
所以我试图进一步推理。这是我想出的:
3.使用与 i
p
的通用奇数 n
我们可以说,对于在 f
(k
) 和 f
(k
) 之间共享的任何因子 k = f * x = fx
,我们最终得到n
也共享那个共同因素 n = f * y = fy
。为什么?
同样,如果我们尝试计算 i = k % n
:
f
那么:
k
仅当且仅当 k = fx = (n * q) + i = (fy * q) + i = fyq + i
也共享 k = fx = fyq + i
作为其因数之一时才能满足,例如i
:
f
通往i = f * g = fg
。
这意味着如果 k = fx = fyq + fg = f(yq + g)
和 yq + g = x
共享一个公因子,那么模 k
的结果也将具有该公因子,因此 n
将始终是那个公因数的倍数,例如对于 i
的 i
和 k
(奇数,不是质数):
K = {12,15,33,96,165,336}
n = 9
和 k | k % n
---------------------------
12 | 12 % 9 = 3
15 | 15 % 9 = 6
33 | 33 % 9 = 6
96 | 96 % 9 = 6
165 | 165 % 9 = 3
336 | 336 % 9 = 3
始终共享一个公因子(在本例中为 k
)。
这导致 n
也是 3
的倍数,因此,再次在这种情况下,使用的哈希表的桶索引只会是那些公因数 i = k % n
的倍数。
因此,虽然 3
的奇数肯定比偶数好(如 2.1. 和 2.2 所述),但我们仍然可能有不需要的3
和 n
共享一个公因数 k
时的数字模式。
因此,如果我们使 n
成为质数 (f
),我们肯定会避免 n
与 n = p
共享公因式 n
(前提是f
),因为素数 k
只能有两个因数:1 和它本身。所以...
4.对 f != p
如果 p
是素数 (n
),我们最终得到:
n
那么:
n = p
暗示由整数除法 k = fx = (q * p) + i = qp + i
产生的商 k = fx = qp + i
可以共享公因子 q
或不共享,即:
k // n
或者:
f
在第一种情况 (q = fz
) 中,我们有:
q = z
所以 q = fz
最终也共享公因子 k = fx = (q * p) + i = (fz * p) + i = fzp + i
,例如i
:
f
例如i = fg
。
在第二种情况 (k = fx = (q * p) + i = (fz * p) + i = fzp + i = fzp + fg = f(zp + g)
) 中,我们有:
zp + g = x
即在第二种情况下,q = z
不会将 k = fx = (q * p) + i = (z * p) + i = zp + i = zp + i
作为其因数之一,因为 i
在其因数中也没有 f
。
因此,当对 zp
使用质数时,好处是 f
的结果可以与 n
共享一个公因数 i = k % n
或根本不共享它,例如对于 f
的 k
和 k
:
K = {56,64,72,80,88,96}
在这种情况下,所有 n = p = 17
共享一个公因子 k | k % n
---------------------------
56 | 56 % 17 = 5
64 | 64 % 17 = 13
72 | 72 % 17 = 4 ---> Common factor f = 4 of k and i
80 | 80 % 17 = 12 ---> Common factor f = 4 of k and i
88 | 88 % 17 = 3
96 | 96 % 17 = 11
,但只有 k
和 f = 4
都有 i = 72 % 17 = 4
和 i = 80 % 17 = 12
共享那个共同因素k
:
i
另外,如果我们采用前面的例子,对于 f
的 72 % 17 = 4 -> (18 * 4) % 17 = (4 * 1)
80 % 17 = 12 -> (20 * 4) % 17 = (4 * 3)
并且我们对 k
使用素数 K = {12,336}
而不是 17
,我们得到:
n
即使在这里,我们也看到只有在这 3 种情况下,9
和 k | k % n
---------------------------
12 | 12 % 17 = 12
15 | 15 % 17 = 15
33 | 33 % 17 = 16
96 | 96 % 17 = 11
165 | 165 % 17 = 12
336 | 336 % 17 = 13
在这种情况下共享公因数 f = 3
:
k
这样,使用质数,冲突的概率降低了,我们可以更好地在哈希表中分布数据。
现在,即使 n
是素数,或者至少是素数的倍数,会发生什么?我认为在这种情况下,沿着哈希表的分布会更好,因为如果 12 % 17 = 12 -> (4 * 3) % 17 = (4 * 3)
15 % 17 = 15 -> (5 * 3) % 17 = (5 * 3)
165 % 17 = 12 -> (55 * 3) % 17 = (4 * 3)
和 k
都是素数或者 k
是素数的倍数,前提是 n
不是素数 k
的倍数。
这是我的结论,为什么质数更适合哈希表的长度。
很高兴收到您对我理解该主题的方式的反馈和想法。
谢谢。
解决方法
说到链接哈希表,您几乎已经有了答案,尽管可以用更少的词来写:
- 数据通常具有模式。例如,内存地址的低位通常为零。
- 许多哈希函数,尤其是非常流行的多项式哈希函数,都是仅使用加法、减法和乘法构建的。所有这些操作都具有结果的最低 n 位仅取决于操作数的最低 n 位的属性,因此这些哈希函数也具有此属性。
- 如果您的表大小在最低 n 位中为零,并且数据在最低 n 位中完全相同,并且您的哈希函数具有上述属性上面...那么你的哈希表将只使用每 2n 个插槽中的一个。
有时我们通过选择一个奇数哈希表大小来解决这个问题。质数更好,因为表大小的每个小因素都会导致类似的问题,哈希值的算术级数不同。
不过,有时我们会通过向哈希表本身添加一个额外的哈希步骤来解决这个问题——一个额外的哈希步骤将哈希的所有位混合在一起并防止此类问题。 Java HashMap
使用大小为 2N 的表,但执行此额外混合步骤以帮助覆盖所有插槽。
那是为了链接哈希表。
对于使用开放寻址来解决冲突的哈希表,通常需要选择主要表大小以确保探测方案最终会检查所有(或至少一半)插槽。这是为了保证表至少在它(半)满之前都可以工作。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。