我们知道数组能够提供对元素的快速访问但难于扩展;链表易于扩展但不能对其元素进行快速访问。对于大量元素的数据来说,我们当然希望两全其美。散列表提供了达到此目标的一种方法。
散列表又称哈希表,它有以下几个术语:
1.关键字:元素的存储部分,数据库的元素通过它进行存储,查找等操作(也称作散列关键字)
2,散列表元:散列数组的某个位置,其后跟着另外一个包含其元素的结构
3,散列函数:对关键字和散列表元提供映射的函数
4,完全散列函数:对关键字和整数提供一一映射的函数
下面是一个简单的散列函数的实例
- unsigned int hash(int keyvalue)
- {
- return (keyvalue % HASH_ARRAY_SIZE);
- }
以下是几种常用的散列函数:
1. 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a•key + b,其中a和b为常数(这种散列函数叫做自身函数)
2. 数字分析法
3. 平方取中法
4. 折叠法
5. 随机数法
6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词
可以想象得出,散列函数的性能直接决定了散列表的性能,当散列函数把太少或太多关键字映射到单个散列表元上时,散列表的使用效率会大大降低。
如果我们使用一个完全散列 函数,在散列表中查找元素的速度最快。该函数保证所有可能的关键字都会分别映射到唯一的散列表元编号上。这就暗示了每个散列表元之多包含一个单一元素,而且散列表实际上使用关键字作为下标的数组。但是通常很难找到一个合适的完全的散列函数。当一个特定散列函数中多个关键字映射到一个散列表元编号,称之为“冲突”。当散列表函数无法改写完善时,处理”冲突“有两个选择:
(1)允许散列表元中放置多个元素,也就是每一个散列表元是一个链表。
(2)调整冲突,以下是几种常用的方法:
1,线性探测法:从冲突的散列表元进行线性查找,直到找到一个空表元为止。
2,再散列法:再进行一次散列,使用一个不同的散列函数,甚至可能用到其他不同的元素关键字。
3,溢出区法:为散列关键字预留出一块区域,这可能是另外一个散列表,或许会用到自己的散列函数。
在使用中我倾向于使用允许一个散列表元放置多个元素的方法,这时实际上散列表就是一个链表数组。如下图:
这样在插入(散列表元有序),删除,查找,实际复杂度都在O(1)与O(L)之间,L是散列表元中元素的最大数目。