散列(2)线性探测法和双重散列法
来源:互联网 发布:网络监控服务器 编辑:程序博客网 时间:2024/05/21 10:43
接上篇 散列的简要描述和链地址法
解决散列冲突的方法:
1. 线性探测法
如果我们能够预测将要存入表中元素的数目,而且我们有足够的内存空间可以容纳带有空闲空间的所有关键字,那么使用链地址法是不值得的。我们依靠空的存储空间解决冲突:设计表长M大于元素数目N,开放地址法,最简单的开放地址法是线性探测法:
初始化
该符号表的实现将元素保存到大小是元素个数两倍的散列表中。
void HashTableInit(int max){ N = 0; M = 2*max; hash_table = new Item[M]; for(int i = 0; i < M; i++) hash_table[i] = NULLItem;}
插入
(1)当冲突发生时,即准备插入的位置已被占用,我们检查表中的下一个位置,
(2)如果下一个位置也被占用,继续检查下一个,知道遇到一个空位,然后将其插入
在搜索时:
void HashTableInsert(Item item){ int hash_key = Hash(GetKey(item), M); while (hash_table[hash_key] != NULLItem) { hash_key = (hash_key+1) % M; } hash_table[hash_key] = item; N++;}
搜索
我们将检查表中是否存在与搜索关键字匹配的元素成为探测。线性探测法的特点是每次探测有3种可能结果:
(1)搜索命中(当前位置上的元素关键字与搜索关键字匹配,停止搜索),
(2)搜索失败(搜索位置为空,停止搜索),
(3)不匹配(搜索位置非空,但是不匹配,继续搜索下一位置)。
Item HashTabelSearch(KeyItem v){ int hash_key = Hash(v, M); //遇到空位置,搜索失败 while (hash_table[hash_key] != NULLItem) { if(eq(GetKey(hash_table[hash_key]), v)) //搜索命中 break; hash_key = (hash_key+1) % M; //不匹配 } return hash_table[hash_key];}
删除
线性探测表中的删除,仅仅移走删除关键字对应的元素时不够的。因为当移走后形成的空位会导致其后面元素的搜索失败(空位终止向后搜索)。因此,应该将删除位置与其右边的下一个空位之间所有元素重新散列插入到表中。
void HashTabelDelete(Item item){ int hash_key = Hash(GetKey(item), M); //寻找删除位置 while (hash_table[hash_key] != NULLItem) { if (eq(GetKey(item), GetKey(hash_table[hash_key]))) break; else hash_key = (hash_key+1) % M; } if (hash_table[hash_key] == NULLItem) return; hash_table[hash_key] = NULLItem; N--; //将删除位置到其右边下一个空位之间所有的元素重新散列 for (int j = hash_key+1; hash_table[j] != NULLItem; j = (j+1)%M) { HashTableInsert(hash_table[j]); hash_table[j] = NULLItem; }}
性能分析
开放地址法的性能依赖于
对于稀疏表(
在线性探测中,多个元素聚合到连续一段空间成为聚焦,这将导致搜索时间的变慢。时间平均开销依赖于插入时的聚焦情况。即:要经历很多次的探测才能确定是否搜索成功(匹配)或失败(空位)。
2. 双重散列表
对于线性探测法,当聚焦问题严重或者表接近满时,要搜索一个关键字,往往要逐个检查很多个无关项(先于和搜索关键字匹配的元素插入)。为了解决聚焦问题,提出了双重散列算法,其基本策略和线性探测法一项,唯一不同是:它不是检查冲突位置后的每一个位置,而是采用另一个散列函数产生一个固定的增量。(跳跃式检查和插入,减小聚焦大小)
假设第二个散列函数值为T
- 线性探测法:逐个检查冲突位置的下一个位置
- 双重散列表:每隔T个位置检查一次
注:第二个散列函数要仔细选择,需满足条件
(1)排除散列值是0的情况
(2)产生的散列值必须与表长M互素
常用`#define Hash2(v) ((v % 97) + 1)
2.1 搜索和插入
void HashTableInsert(Item item){ int hash_key = Hash(GetKey(item), M); int hash_key2 = Hash2(GetKey(item), M); while (hash_table[hash_key] != NULLItem) { // hash_key = (hash_key+1) % M; 线性探测时 +1 hash_key = (hash_key+hash_key2) % M; } hash_table[hash_key] = item; N++;}Item HashTabelSearch(KeyItem v){ int hash_key = Hash(v, M); int hash_key2 = Hash2(v, M); while (hash_table[hash_key] != NULLItem) { if(eq(GetKey(hash_table[hash_key]), v)) break; hash_key = (hash_key+hash_key2) % M; } return hash_table[hash_key];}
2.2 删除
如果双重散列表的删除操作继承线性探测算法:那么删除算法会降低双重散列表的性能,因为待删除关键字有可能影响整个表中的关键字,解决办法是:用一个观察哨代替已删除元素,表示该位置被占用,不与任何关键字匹配。
2.3 性能分析
如果要保证所有搜索的平均开销小于t次探测,那么线性探测法和双重散列法的装填因子分别要小于
3. 动态散列表
因为随着表中关键字的增多,表的性能会下降,一种解决办法是:当表快要满时,表的大小加倍,然后被所有元素重新插入。(非经常性操作,可以接受)
如果表支持删除的ADT操作,随着表元素减少,值得对表大小减半。但需要注意的是表长加倍和减半的阈值时不同的。如在半满时加倍,在1/8满时减半。
表长动态变化能够合理处理各种元素个数的变化,缺点是表的扩张和缩减时的重新散列和内存分配的开销。
4. 散列的综述
- 线性探测是三者中最快的(前提是内存足够大保证表稀疏)
- 双重散列法使用内存最高效(需要额外开销计算第二个散列值)
- 链地址法易于实现(假设已经存在好的内存分配),特别对于删除操作;对于固定表长通常选择链地址法。
如何选择:
- 是选择线性探测还是双重散列主要取决于:计算散列函数的开销和装填因子。
- 链地址法需要额外的内存空间存储链接,但是有一些符号表中已经事先分配好了链接字段的元素。虽然不如开放地址法快,但性能仍然比顺序搜索快的多。
- 当以搜索为主且关键字数目不能精确预测时,动态散列表是可选的。
5. 所有源码
http://download.csdn.net/detail/quzhongxin/8620465
参考资料 《算法:C语言实现》p388-401
- 散列(2)线性探测法和双重散列法
- 散列技术之线性探测法
- 数据结构:散列2(探测法)
- HashTable哈希表/散列表(线性探测和二次探测)
- Javascript数据结构算法之散列(霍纳算法,开链法,线性探测-寻址法)
- 线性探测可再散列的散列
- 散列表--线性探测法
- 散列表(拉链式和线性探测)
- 平方探测和线性探测解决散列表冲突的区别(优点及缺点)
- java 解决Hash(散列)冲突的四种方法--开放定址法(线性探测,二次探测,伪随机探测)、链地址法、再哈希、建立公共溢出区
- 4C的练习5-39 整型关键字的散列映射 <线性探测法>
- 数据结构与算法之散列(线性/平方/双平方探测法)<八>
- 基于线性探测法的散列表
- 哈希表(线性探测法处理冲突)
- 哈希表---线性探测法
- 线性探测法hash
- 散列存储(开放地址法-双重散列)
- hash表线性探测法
- 关于右值引用的一个错误。
- 银联在线支付---利用测试案例代码模拟支付应用(修改)
- 读取excel 工具类
- Android f_rndis 分析笔记
- RFID系统二进制树型搜索算法是如何解决碰撞的?简述其实现步骤
- 散列(2)线性探测法和双重散列法
- Qt for Android - ANT_HOME is set incorrectly or ant could not be located
- Wireless Network(POJ-2236)(并查集)
- 第2章 8
- 树的遍历、平衡二叉树实现
- robotframework如何提取失败的测试,以便下次运行
- 垃圾收集器与内存分配策略
- 向文件中写入时文件中没有任何内容
- 第四章实验第三题