Chinaunix首页 | 论坛 | 博客
  • 博客访问: 332488
  • 博文数量: 125
  • 博客积分: 30
  • 博客等级: 民兵
  • 技术积分: 160
  • 用 户 组: 普通用户
  • 注册时间: 2012-12-06 15:18
文章分类

全部博文(125)

文章存档

2014年(29)

2013年(93)

2012年(3)

分类: C/C++

2013-08-08 11:14:42


1. 哈希表概念                                                        
      对于动态查找表而言,1) 表长不确定;2)在设计查找表时,只知道关键字所属范围,而不
   知道确切的关键字。因此,一般情况需建立一个函数关系,以f(key)作为关键字为key的
   录在表中的位置,通常称这个函数f(key)为哈希函数。(注意:这个函数并不一定是数学函
   数)
 例如:对于如下9个关键字
从这个例子可以看出:
 1. 哈希函数是一个映象,即:将关键字的集合映射到某个地址集合上,它的设置很灵活,
   只要这个地址集合的大小不超出允许范围即可; 
 2. 由于哈希函数是一个压缩映象,因此,在一般情况下,很容易产生“冲突”现象,即:
   key1 key2,而 f(key1) = f(key2) 并且,改进哈希函数只能减少冲突,而不能避免冲突。
  因此,在设计哈希函数时,一方面要考虑选择一个“好”的哈希函数;另一方面要选择一种
  处理冲突的方法。所谓“好”的哈希函数,指的是对于集合中的任意一个关键字,经哈希函
  数“映象”到地址集合中任何一个地址的概率是相同的,称这类哈希函数为“均匀的”哈希
  函数。
  根据设定的哈希函数H(key)和所选中的处理冲突的方法,将一组关键字映象到一个有限的地
  址连续的地址集(区间)上,并以关键字在地址集中的“象”作为相应记录在表中的存储位置,
  这种表被称为哈希表。哈希表是基于哈希函数建立的一种查找表。

2. 哈希函数的构造方法
  对数字的关键字可有下列哈希函数的构造方法,若是非数字关键字,则需先对其进行数字化
   处理。
(1) 直接定址法
  哈希函数为关键字的线性函数:
   H(key) = key 或者 H(key) = a key + b
  适用场合:地址集合的大小 = 关键字集合的大小 
(2)数字分析法
  假设关键字集合中的每个关键字都是由s位数字组成(k1, k2, …, kn),分析关键字集中的
  全体,并从中提取分布均匀的若干位或它们的组合作为地址。
  适用场合:能预先估计出全体关键字的每一位上各种数字出现的频度。
(3) 平方取中法
  若关键字的每一位都有某些数字重复出现频度很高的现象,则先求关键字的平方值,以通过
  “平方”扩大差别,同时平方值的中间几位受到整个关键字中各位的影响;
(4) 折叠法 
  若关键字的位数特别多,则可将其分割成几部分,然后取它们的叠加和为哈希地址。可有:
  移位叠加和间界叠加两种处理方法。
(5) 除留余数法 
   H(key) = key MOD p p≤m (表长)
  关键问题是:如何选取 p ?
  p 应为不大于m 的质数或是不含20以下的质因子
  例如:key = 12, 39, 18, 24, 33, 21 时, 若取 p=9, 则使所有含质因子3的关键字均映射
     到地址0, 3, 6 上,从而增加了“冲突”的可能性。
(6) 随机数法
  H(key) = Random(key)实际造表时,采用何种构造哈希函数的方法取决于建表的关键字集合
   的情况(包括关键字的范围和形态),总的原则是使产生冲突的可能性降到尽可能地小。

3. 处理冲突的方法
  处理冲突的实际含义是:为产生冲突的地址寻找下一个哈希地址。
(1)开放定址法 
  为产生冲突的地址H(key)求得一个地址序列:
  H0, H1, H2, …, Hs 1≤s≤m-1
  其中:H0 = H(key)
  Hi = ( H(key) + di ) MOD m i=1, 2, …,s
  增量 di 有三种取法:
  1) 性探测再散列 
   di = c i 最简单的情况 c=1
  2) 方探测再散列 
   di = 12, -12, 22, -22, …,
  3) 机探测再散列 
   di 是一组伪随机数列
   注意:增量di应具有“完备性”,即产生的Hi均不相同,且所产生的s(m-1)个Hi值能覆盖
       哈希表中所有的地址。要求:
   · 平方探测时的表长m必为4j+3的质数;
   · 随机探测时的m和di没有公因子。
(2)链地址法 
   将所有哈希地址相同的记录都链接在同一链表中。
   线性探测容易产生二次聚集,链地址肯定不会产生二次聚集。一次聚集的产生主要取决于    哈希函数,在哈希函数均匀的前提下,可以认为没有一次聚集。 

4. 哈希表的查找
  查找过程和造表过程一致。假设采用开放定址处理冲突,则查找过程为:
  对于给定值K, 计算哈希地址 i = H(K) ,若r[i] = NULL 则查找不成功;
  若 r[i].key = K 则查找成功, 否则求下一地址Hi,直至r[Hi] = NULL (查找不成功)
  或r[Hi].key = K (查找成功)为止。
 
//--- 开放定址哈希表的存储结构 ---
int hashsize[] = { 997, ... }; // 哈希表容量递增表,一个合适的素数序列
typedef struct {
ElemType *elem; // 数据元素存储基址, 动态分配数组
int count; // 当前数据元素个数
int sizeindex; // hashsize[sizeindex]为当前容量
} HashTable;
#define SUCCESS 1
#define UNSUCCESS 0
#define DUPLICATE -1 
Status SearchHash (HashTable H, KeyType K, int &p, 
int &c) {
// 在开放定址哈希表H中查找关键码为K的元素,若查找成功,以p指示待查数据元素在表中//位置,并返回SUCCESS;否则,以p指示插入位置,并返回UNSUCCESS, c用以计冲突次数,//其初值置零,供建表插入时参考
p = Hash(K); // 求得哈希地址
while ( H.elem[p].key != NULLKEY && !EQ(K, H.elem[p].key)) 
// 该位置中填有记录并且关键字不相等
collision(p, ++c); // 求得下一探查地址p
if (EQ(K, H.elem[p].key)) return SUCCESS; // 查找成功,p返回待查数据元素位置
else return UNSUCCESS;// 查找不成功,p返回的是插入位置
} // SearchHash

通过调用查找算法实现了开放定址哈希表的插入操作。
Status InsertHash (HashTable &H, Elemtype e) {
// 查找不成功时插入数据元素e到开放定址哈希表H中,并返回OK;若冲突次数过大,则//重建哈希表
c = 0;
if ( HashSearch ( H, e.key, p, c ) == SUCCESS )
return DUPLICATE; // 表中已有与e有相同关键字的元素
else if ( c < hashsize[H.sizeindex]/2 ) { // 冲突次数c未达到上限,(阀值c可调)
H.elem[p] = e; ++H.count; return OK; // 插入e
else RecreateHashTable(H); // 重建哈希表
} // InsertHash
可见,无论查找成功与否,ASL均不为零。

决定哈希表查找的ASL的因素:
1. 选用的哈希函数; 
2. 选用的处理冲突的方法; 
3. 哈希表饱和的程度,装载因子α=n/m 值的大小一般情况下,可以认为选用的哈希函数是
  “均匀”的,则在讨论ASL时,可以不考虑它的因素。
 哈希表的ASL是处理冲突方法和装载因子的函数。可以证明查找成功时有下列结果:

5. 哈希表的删除操作 
  从哈希表中删除记录时,要作特殊处理,相应地要修改查找算法对静态查找表,有时
 也可能找到不发生冲突的哈希函数。 即此时的哈希表的ASL=0, 此类哈希函数为理想
(perfect)的哈希函数

阅读(672) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~