欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

散列

程序员文章站 2022-03-15 21:07:49
...

前面我们讲过字典,虽然我们说过通过使用查找方法来提高处理效率,但是这样的效果其实并不是很好,因此我们还需要使用一种更为高效的字典组织方法,其中有一种方法就是我们今天要介绍的散列

散列又称为哈希或杂凑。散列方法在表项的存储位置与它的关键码之间建立一个确定的对应函数关系Hash()

Address = Hash(Record.key)

通过一个函数计算存储位置,并按照此位置存放,这种方法就被称为散列方法。基于散列方法构建的数据结构就被称为散列表。

其实,散列表也有两种表达方式,一种是哈希图(Hash maps),另外一种是哈希集(Hash sets),其中哈希图是存储<键值-属性>对的关联结构,哈希集则是一个用来记录集合中项目成员的结构。


散列函数

接下来我们介绍几种常见的散列函数。

1.直接定值法

直接定址法利用元素关键码的某个线性函数值作为该元素的散列地址。
(,为常数)
这种方法的有点在于实现方式简单,算法时间复杂度较小,而且不会产生冲突。但这种方法要求的散列地址空间的大小与关键码集合的大小要一致才可以,这一般是很难实现的。

2.除留取余法

设散列表中允许的地址数为,取一个不大于,但最接近或等于的质数,或选取一个不含有小于20的质因子的合数作为除数,利用下面的公式就可以计算出来散列地址,这种方法称为除留取余法

这种方法将有效缩减散列地址空间的大小,但这种方法是可能会出现冲突的,具体冲突的处理方法我们之后会进行讲解。

3.平方取中法

平方取中法是一种先放大再集合的构造方法,这种构造模式先通过求关键字的平方值扩大相近数的差别,然后根据表长度取中间的几位数作为散列函数值,这种取中间数的方法是一种类随机方案,因此也可以认为平方取中法是一种产生伪随机数的方法。
具体过程如下:
1)利用一定的编码规则把元素的关键码转换成为标识符
2)求出标识符的内码表示,并计算内码的平方值
3)取内码平方数的中间位作为元素最终的散列地址
这种方法是构造散列函数的常用方法,这种方法适合于关键码中每一位都有某些数字重复出现的情况。

4.乘余取整法

这种方法是利用下面这个式子来计算元素的散列地址的:
Z(akey%1)
其中,是一个大于0小于1的常数。为一个整数,括号里面的式子表示对其取小数部分,即
key%1=akey-akey
这种方法不但会缩减散列地址空间的大小,还能极大减小冲突情况的发生概率。

5.折叠法

所谓折叠法是将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位),这方法称为折叠法。这种方法又分为移位法分界法


字符串散列

1.BKDR散列
unsigned long BKDRHash(const string& str)
{
    unsigned long seed = 31;
    unsigned long hashval = 0;
    
    for(int i = 0; i < str.length(); i++)
        hashval = hashval * seed + str[i];
    return hashval % HASHSIZE
}

这种方法是一种非常经典的散列算法,而且在这里使用的是无符号算术运算,可以保证散列值非负。

2.RS散列
unsigned long RSHash(const string& str)
{
    unsigned long a = 31415, b = 27183;
    unsigned long hashval = 0;
    for(int i = 0; i < str.length(); i++)
    {
        hashval = (hashval * a + str[i]) % HASHSIZE;
        a = a * b % (HASHSIZE-1);
    }
    return hashval;
}

该算法采用伪随机系数替代了固定基数,并且不使用预先计算出的一组随机数作为系数。
上面代码中的,是可以任意取值的。

3.FNV散列

FNV能快速地处理大量数据并保持较小的冲突率,它的高度分散性使它在处理一些非常相近的字符串时表现尤为突出。
FNV分为两个版本,FNV-1和FNV-1a,伪代码如下:

// FNV-1
hash = offset_basis
for each octet_of_data to be hashed
    hash = hash * FNV_prime
    hash = hash xor octet_of_data
return hash

// FNV-1a
hash = offset_basis
for each octet_of_data to be hashed
    hash = hash xor octet_of_data
    hash = hash * FNV_prime
return hash

这种散列算法会将结果映射到一个定长的大整数当中,通常以十六进制数表示。


处理散列冲突

在这里我们主要介绍的是双重散列法
这种方法是开放定址法中最好的方法之一。它使用两个散列函数Hash()ReHash(),在使用该方法时,先用Hash()来计算表项所在的位置,一旦发生冲突,再使用ReHash()方法来重新计算位置。定义ReHash()的方法很多,但无论采用什么方法定义,都必须使ReHash()的值和互为素数,这样才能使发生冲突的同义词地址均匀地分布在整个表中。