散列表——平方探测——二次剩余

散列表的平方探测

我们先回顾一下散列表(Hash Table)的平方探测(Quadratic Probing):

平方探测 是一种用于解决哈希冲突的方法。哈希冲突发生在两个或多个不同的键通过哈希函数映射到同一个索引位置时。平方探测通过改变冲突位置的探测方式,来减少冲突并均匀分布键值。
计算公式为:探测位置=(初始哈希位置+i^2)%表长

再来看看《数据结构与算法分析——C语言描述(Mark Allen Weiss 第二版 机械工业出版社)》119页定理5.1的证明,这是一个有关平方探测的重要结论,这里的证明似乎不是很完整。

定理5.1:如果使用平方探测,且表的大小是素数,那么当表至少有一半是空的时候,总能够插入一个新的元素。

证明的倒数第二行

“因此任何元素都有 TableSize/2\lfloor TableSize/2\rfloor 个可能被放到的位置。”

这句话乍一看并不显然,因为定理先证明了前 TableSize/2\lfloor TableSize/2\rfloor 个备选位置是互异的,但是并没有说明后面的 TableSize/2\lfloor TableSize/2\rfloor 是什么情况。有可能后面可能的情况与前面不同,导致任何元素都有 TableSizeTableSize 个可能的位置。那按书中的说法,应该在此句的 TableSize/2\lfloor TableSize/2\rfloor 前加“至少”。
但是我们下面会证明,书中的说法是对的,即只有 TableSize/2\lfloor TableSize/2\rfloor 种可能的结果。也就是说,后面一半的备选位置和前面一半是相同的。

平方探测是在计算冲突元素在发生第 ii 次冲突将被放到的新位置:
F(x,i)=x%p+i2F(x,i) = x \%p+i^2,假设 pphashtablehash table 的大小,为奇素数,hash(x)=x%phash(x)=x\%p ,

那么更确切地,F(x,i)=(x%p+i2)%p=(x%p+i2%p)%pF(x,i) = (x \%p+i^2)\%p=(x\%p+i^2\%p)\%p
这是因为(a+b)%p=(a%p+b%p)%p(a+b)\%p=(a\%p+b\%p)\%p.

上面 F(x,i)F(x,i)的结果在 xx 固定的情况下只取决于 ii,即冲突发生的次数。所以只考虑 i2%pi^2\%pii 取遍正整数时所有的可能结果即可。结果不会超过pp个(因为模 pp 的同余类只有pp种)。

实际上,i2%pi^2\%pii 取遍正整数时所有的可能结果就是模 pp 的所有二次剩余,结果的个数就是二次剩余的个数加一,下面将会证明模 pp 的二次剩余个数为 p12\frac{p-1}{2}

二次剩余的定义

二次剩余的定义如下:

一个正整数aa称为模pp的二次剩余,如果存在某个整数ii使得i2a(modp)i^2\equiv a \pmod{p} 成立,否则称为模pp的二次非剩余。

我们使用如下记号:勒让德符号(Legendre)来表示二次剩余关系

(ap)={1,ap的二次剩余1,ap的二次非剩余0,pa(\frac{a}{p})= \begin{cases} 1, & a \text{是} p \text{的二次剩余}\\ -1, & a \text{是} p \text{的二次非剩余}\\ 0, & p|a \end{cases}

二次剩余的个数

现在我们来求解模 pp的二次剩余的个数,即 i2a(modp)i^2\equiv a \pmod{p}ii 取遍正整数时,正整数 aa的个数。

先考虑 ii 的取值,显然只要从1取到p即可,若i>pi>p,qZ,i=qp+r,0<r<p\exists q\in Z, i=qp+r, 0<r<p 此时 i2%p=r2%pi^2\%p=r^2\%p,仍只需考虑小于p的情况即可。

1x<yp1\leq x < y \leq p,且x2%py2%px^2\%p\equiv y^2\%p.
故有 p(x2y2)p|(x^2-y^2) ,即 p(x+y)(xy)p|(x+y)(x-y)
x,yx,y的取值范围可知 p(x+y)p|(x+y),进一步地, x+y=px+y=p

于是我们得到了这样的关系:当两个数和为模数的时候,它们的平方模除这个模数结果相同。因此,对于 (1,p1),(2,p2),(1,p-1),(2,p-2),···p12\frac{p-1}{2} 个数对(别忘了p为奇素数),处于同一数对中的数,它们的平方模除pp的结果相同,不同数对中的元素模除pp结果不同,共有 p12\frac{p-1}{2} 个结果。

因此二次剩余的个数就是 p12\frac{p-1}{2}

二次剩余的检验

有时候,我们已知 i2a(modp)i^2\equiv a \pmod{p} 式中的a,pa, p要去求满足条件的 ii(这就是求解二次剩余方程)。但是这样的ii并不一定存在(这种情况就称a是模p的二次非剩余)。

当然也有很多情况,我们不知道模除 pp 的二次剩余是什么,这时候 ii 就更不知道了,不过我们现在已经知道了二次剩余的个数,所以只需从1到 p12\frac{p-1}{2} 之间一个一个检验就行。那么如何检验一个数它是不是模除p的二次剩余呢?欧拉告诉我们有欧拉准则(Euler Criterion)

(ap)ap12(modp)={1(modp)如果 a 是模 p 的二次剩余1(modp)如果 a 不是模 p 的二次剩余(\frac{a}{p})\equiv a^{\frac{p-1}{2}}\pmod{p}= \begin{cases} 1 \pmod{p} & \text{如果 } a \text{ 是模 } p \text{ 的二次剩余} \\ -1 \pmod{p} & \text{如果 } a \text{ 不是模 } p \text{ 的二次剩余} \end{cases}

所以回到我们的问题上来,冲突时发生时,利用平方探测 hashtablehashtable 中所有备选的位置,就是模 TableSizeTableSize 的二次剩余 (i2%pi^2\%p) 加上一个 x%px\%p 再模除 pp,但是我们只用考虑个数,它有 TableSize/2\lfloor TableSize/ 2\rfloor 个。
于是,再看定理5.1,表至少一半空时,二次剩余至少有两个,所以一定有空的备选位置,故一定可以放下一个新元素。

散列平方探测插入失败问题

最后来看一个判断题,正是这篇文章的缘起。

If 7 elements have been stored in a hash table of size 13 at positions { 0, 1, 2, 4, 5, 10, 11 }, and the
hash function is H(x) = x%13. Then an empty spot can’t be found when inserting the element 40
with quadratic probing.

答案是✔
一般的解法是不断用 (40+i2)mod13(i=0,1,2,)(40+i^2) mod{13}(i=0,1,2, \cdots) 求得新的插入位置,计算后发现结果是 1,2,5,10,4,0,11,11,0,4,10,5,21, 2, 5, 10, 4, 0, 11, 11, 0, 4, 10, 5, 2
这13个数循环,全是题中已插入的位置,所以40不能成功插入。

现在我们可以利用上面对二次剩余的讨论对这个循环给出解释。
先转换一下插入位置表达式:
(40+i2)mod13=(40mod13+i2mod13)mod13=(1+i2mod13)mod13,(i=0,1,2,)(40+i^2) mod{13}=(40 mod{13}+i^2 mod{13})mod{13}=(1+i^2 mod{13})mod{13},(i=0,1,2, \cdots)
这个结果可能的取值个数(也就是可插入位置个数),取决于 $i^2mod{13}的值的个数 $。

首先,可以确定模除13的二次剩余的个数:p12=1312=6\frac{p-1}{2}=\frac{13-1}{2}=6
在这道题里,模数的结果可以为0,故在散列中有 6+1=76+1=7 个可插入位置。接着我们就仅需判断所给的positions是否为这7个可插入位置。(实际上得到的循环已经证实了确实是这7个)

现在可以使用上面的欧拉准则,将所给的position标号减去1后求6次方然后模除13,看看结果是不是1(此时即为模13的二次剩余)或者0。
注意:0减去1结果取12,因为 1+12=130mod131+12=13\equiv0 mod{13}

下面是验证的Python代码:

1
2
3
4
5
6
7
8
positions=[0,1,2,4,5,10,11]
res=[0,1]
p=13
for position in positions:
if position==0:
position=13
if (position-1)**((p-1)/2)%13 in res:
print(f"position {position%13} is not insertable")