Hash
它是什么?
哈希表是又称散列表,一种以 “key-value” 形式存储数据的数据结构。所谓以 “key-value” 形式存储数据,是指任意的 key 都唯一对应到内存中的某个位置。只需要输入查找的值 key,就可以快速地找到其对应的 value。可以把哈希表理解为一种高级的数组,这种数组的下标可以是很大的整数,浮点数,字符串甚至结构体。
为什么存在?
有时关键码空间的数量级可能远远大于实际问题的空间,造成了巨大的浪费,我们使用桶(bucket)直接存放或间接指向一个词条。
优缺点?
优点:
>>>空间利用率 :
问题空间N,关键码空间R,桶数组(bucket array)或散列表(hash table),容量为M,则:
N < M << R
空间 = O( N + M )=O(N)
M尽可能与N同阶,所以至少与使用关键码空间相比空间利用率大大的提高了。
>>>常数级的查找时间 :
因为哈希表遵循的是循值访问,所以查找时间只需要O(1),这是再好不过的了。
缺点:
>>>冲突:
hash(key) = key % M
冲突是无法避免的,但是我们可以尽量减少冲突,并从以下两个方向入手:
- 精心设计散列表及散列函数,以尽可能降低冲突的概率;
- 制定可行的预案,以便在发生冲突时,能够尽快予以排解。
怎么用?
>>>循值访问
>>>散列函数的设计:
- 除余法 :hash(key) = key % M
M应当选取素数。步长为step,gcd(step,M)=G,当且仅当 G == 1时,足迹能够遍布整个散列表。又由于step不能确定,所以M应是一个素数。
- MAD法 (multiply-add-divide) :hash(key) = ( a * key + b ) % M
除余法有两个缺陷。
一:它有不动点。无论表长M取值如何,总有hash(0) ≡ 0
二:零阶均匀。[ 0 ,R)的关键码,平均分配至M个桶;但相邻的关键码的散列地址也比相邻。
取M为素数,a > 0,b > 0,a % M != 0。hash(key) = ( a * key + b ) % M
- 平方取中 (mid-square) :取 key^2的中间若干位,构成地址
原理:将平方运算分解为一系列的左移操作,以及若干次加法,思想类似于快速幂,如13^2=13 + (13)<<2 + (13)<<3。如果忽略进位,每个数位都是由原关键码若干次求和得到的,因此两侧的数位是由更少的原数位累积而得,而越是居中的数位是由更多的原数位累积,截取居中的若干位,可以使得原关键码各数位对最终地址的影响彼此更为接近。
-
多项式法 :
h a s h ( s = x 0 , x 1 , x 2 , ⋅ ⋅ ⋅ , x n − 1 ) = x 0 ∗ a n − 1 + x 1 ∗ a n − 2 + ⋅ ⋅ ⋅ + x n − 2 ∗ a 1 + x n − 1 hash( s = x_0,x_1,x_2,···,x_{n-1} ) = x_0*a^{n-1} + x_1*a^{n-2} + ··· + x_{n-2}*a^1 + x_{n-1} hash(s=x0,x1,x2,⋅⋅⋅,x