开放寻址——线性探测

https://www.cnblogs.com/hongshijie/p/9419387.html
分离链接散列算法还有一个亟待解决的缺点:需要指针,由于给新单元分配地址需要时间,这就导致了速度减慢,所以不太好。还有,因为链表是次第关联的结构,实现算法的代码自身的复杂程度和出错概率会大大增加。而只要采用这种策略,就很难保证每组冲突的词条在空间上能够彼此毗邻,因为动态分配的节点在内存里不一定是连续的,这样一来会导致一个致命缺陷:对于稍大规模的词条集合,查找中将做大量的I/O操作,无法利用系统预先缓存,导致效率低下。

因此或许我们应该放弃这种策略,并反其道而行之,仅仅依靠基本的散列表结构,就地排解冲突反而是更好的选择。也就是采用所谓的开放定址策略,它的特点在于:散列表所占用的空间在物理上始终是地址连续的一块,相应的所有的冲突都在这块连续空间中加以排解。而无需向分离链接那样申请额外的空间。对!所有的散列以及冲突排解都在这样一块封闭的空间内完成。

因此相应地,这种策略也可以称作为闭散列。如果有冲突发生,就要尝试选择另外的单元,直到找到一个可供存放的空单元。具体存放在哪个单元,是有不同优先级的,优先级最高的是他原本归属的那个单元。从这个单元往后,都按照某种优先级规则排成一个序列,而在查找的时候也是按着这个序列行进,每个词条对应的这个序列被称为探测序列or查找链。

抽象来说,就是我们遇到冲突后,会相继尝试h0(x),h1(x),h2(x)这些单元,其中hi(x)= ( Hash( x ) + F ( I ) ) % TableSize,并且约定F(0)=0,F(x)是解决冲突的方法,就是刚才说的那个“优先级规则”。因为所有的数据都要放在这块空间,所以开放定址所需要的表规模比分离链接要大。通常而言开放定址法的装填因子lambda应该低于0.5。而根据对不同F(x)的选择,学界划分出三种常用的探测序列:线性探测法、平方探测法、双散列

1.线性探测法

在线性探测法中,函数F是关于i的线性函数,典型的情形是F(i)=i。这相当于逐个探测每个单元(必要时可以绕回)以查找出一个空单元。下面显示了将{89,18,49,58,69}插入到一个散列表中的情况(竖着看),使用了和之前一样的散列函数hash(x)=x%size,他们有冲突怎么办?用F(i)=i这个方法,每次从i=0开始尝试,那么根据hi(x)= ( Hash( x ) + F ( I ) ) % TableSize就可以计算出各自不相冲突的地址了。完美!(暂时的)

我们脑内单步调试一下:第一个冲突在49产生:(49%10+0)%10=9,被89占了,那接着往后试,i=1,(49%10+1)%10=0,空的,放入这个空闲地址,这个地址是开放的。58依次和18,89,49产生冲突,试选三次后才找到一个空单元。对69的冲突也如此解决,一旦冲突,试探紧邻其后的单元,直至找到空单元or抵达散列表末尾。线性探测序列0->1->2->3在物理上保持连贯性的,具有局部性,这样一来系统的缓存作用将得到充分发挥,而对于大规模的数据集,这样一来更是可以减少I/O的次数。只要表足够大,总能找到一个空闲单元,但是这太费时间了。更糟的是——就算一开始空闲区域多,经过多次排解冲突后,数据所占据的单元也会开始形成一些区块,聚集在一起,被称为一次聚集(primary clustering),但在前面动机篇里说过,散列函数的初衷是避免数据扎堆,所以后面必须改进。

那么总体看来散列到区块的任何关键字都需要多次试选单元才能解决冲突,然后被放到对应的那个区块里。下面做一个总结

优点:

无需附加空间(指针、链表、溢出区)
探测序列具有局部性,可以利用系统缓存,减少IO
缺点:

耗费时间>O(1)
冲突增多——以往的冲突会导致后续的连环冲突,发生惨烈的车祸

光看这个图可能没什么感觉,举个例子吧,这样感触更深。我们开一个size=7的散列表,也保证了size是素数。把{0,1,2,3,7},就按这个顺序依次插入。前四个数都没问题,依次插入没有冲突。

但是为了插入7,我们先试探0发现非空,往后走,依次试探1,2,3都非空,直到4可以放进去。

在这个散列表的生存期里只有1个发生冲突。看似很棒对吧,再来看另一插入次序:{7,0,1,2,3}。

插入7没问题,但插入0的时候就有冲突了,实际上自此之后每一个数插入都会遇到冲突,前后对比可以看出,第二种插入顺序发生的很多冲突本来是可以避免的。这个时候想必我们改进这种策略的意愿就十分迫切了。

要支持词条的删除则需要格外的小心,现在我们来做一探讨。按照线性探测的规则,先后插入彼此冲突的一组词条都将存放在同一个查找序列中,而更确切的讲:它们应该按照逻辑次序构成整个查找链的一个前缀,其中不得有任何的空桶缝隙。因此词条的删除操作需要做额外的一些处理,如果我们不做一些事先准备,直接将词条删除(就类似对于链表,删除节点的时候不做链条调整,而直接free那个单元,那不直接凉了),就会造成查找链断裂,后续词条丢失——明明存在但访问不到。

对于这种连续空间的单元删除,一个直观的构想是:将后续词条悉数取出,再重新插入。但这太特么慢了,时间复杂度爆炸。其实对于这个问题有一种典型的处理手法:lazy delete,仅做一个删除标记,比如里面预留一个del变量,设置为TRUE。

那么在日后查找中,遇到他之后就应该越过继续往后查找而不是在此返回。在插入时遇到,就把它视作一个空单元,数据覆盖即可。应该说针对开放定址策略,懒惰删除不仅是“不得已而为之”的方法,甚至可以说是针对这种情况的最优方法。因为毕竟在开放定址策略中,每一个桶单元都同时属于多个查找链

开放寻址散列的分析
定理11.6 :
一个装载因子a=n/m<1的开放寻址散列表,在一次不成功的查找中,期望的探查数之多为1/(1-a).假设散列是均匀的。
推论11.7
平均情况,向一个装载因子为a的开放寻址散列表中插入一个元素时,至多需要做1/(1-a)次探查。假设采用的是均匀散列。
定理11.8
给定一个装载因子为a<1的开放寻址散列表,一次成功查找中的期望探查数之多为(1/a)ln(1/(1-a)).

#include "iostream"
#include "math.h"
#define Null -1
#define DELETED -2
using namespace std;//散列函数(全域散列在“全域散列解决哈希冲突中已讲”)
int Hash_div(int val, int m)//除法求余。val值,m为槽的个数(是一个不为2的幂的质数)
{return val % m;
}int Hash_multi(int val, int m)//乘法散列。m一般为2的某次幂
{double A = (sqrt(5) - 1) / 2;return ((val*A - int(val*A)) * m);
}//开放寻址方法:线性探查、二次探查、双重探查
int Line_Prob(int val, int m, int i) //m为槽的个数(是一个不为2的幂的质数),m越大查找次数就越少
{return (Hash_div(val, m) + i) % m;
}int Twice_Prob(int val, int m,int i)
{int c1 = 1, c2 = 3;return (Hash_div(val, m) + c1 * i + c2 * i*i) % m; //其中常数c1,c2,m的值的选择是受限制的
}
int Double_Probe(int val, int m, int i)
{return (Hash_div(val, m) + i * (1 + Hash_div(val, m-1))) % m; //其中m为质数,还可以选择m为2的幂,并设置一个总产生奇数的hash_div2
}
int hash_insert1(int T[], int m, int k)
{int i = 0;do{int j = Line_Prob(k, m, i);//这里可以替换成二次,双重探查。插入,查找,删除函数同时被替换if (T[j] == Null || T[j] == DELETED){T[j] = k;return j;}else i++;} while (i != m);cerr << "hash table overflow" << endl;
}
int hash_search(int T[], int m, int k)
{int i = 0, j = 0;do{j = Line_Prob(k, m, i);//这里可以替换成二次,双重探查。插入,查找,删除函数同时被替换if (T[j] == k){return j;}else i++;} while (T[j] != Null || i != m);return Null;
}
void hash_delete(int T[], int m, int k)
{int j = hash_search(T, m, k);//首先先找到该关键字kif (j != Null){T[j] = DELETED;//如果找到了,那么设置其为空。cout << "删除成功!" << endl;}else cout << "待删除的数据不在表中!" << endl;
}
void Display(int T[],int m)
{cout << "散列表信息显示:"  ;for (int i = 0; i < m; i++){cout << T[i] << " ";}cout << endl;
}
void main()
{int a[9] = { 10,22,31,4,15,28,17,88,59 };int T[19] = { 0 };for (int i = 0; i<19; i++){T[i] = Null;}for (int i = 0; i<9; i++){hash_insert1(T, 19, a[i]);}Display(T,19);hash_delete(T, 19, 10);Display(T, 19);system("pause");
}

开放寻址——线性探测相关推荐

  1. Java解决Hash(散列)冲突的四种方法--开放地址法(线性探测,二次探测,伪随机探测)、链地址法、再哈希、建立公共溢出区

    Java解决Hash(散列)冲突的四种方法--开放地址法(线性探测,二次探测,伪随机探测).链地址法.再哈希.建立公共溢出区 参考文章: (1)Java解决Hash(散列)冲突的四种方法--开放地址法 ...

  2. 【数据结构笔记40】哈希表冲突处理方法:开放地址法(线性探测、平方探测、双散列、再散列),分离链接法

    本次笔记内容: 11.3.1 开放定址法 11.3.2 线性探测 11.3.3 线性探测-字符串的例子 11.3.4 平方探测法 11.3.5 平方探测的实现 11.3.6 分离链接法 文章目录 冲突 ...

  3. Hash(散列)冲突解决之线性探测再散列和二次探测再散列

    线性探测再散列 H(key) = key %13,key 为关键字,采用开放地址法中的线性探测再散列解决冲突,依次输入11 个关键字,16,74,60,43,54,90,46,31,29,88,77, ...

  4. Hash(散列)冲突解决 线性探测再散列和二次探测再散列

    线性探测再散列 例如  哈希函数为: H(key) =  key %13,key 为关键字,采用开放地址法中的线性探测再散列解决冲突,依次输入 11 个关键字,16,74,60,43,54,90,46 ...

  5. 散列(2)线性探测法和双重散列法

    接上篇 散列的简要描述和链地址法 解决散列冲突的方法: 1. 线性探测法 如果我们能够预测将要存入表中元素的数目,而且我们有足够的内存空间可以容纳带有空闲空间的所有关键字,那么使用链地址法是不值得的. ...

  6. 【哈希冲突解决】线性探测再散列和二次探测再散列

    定义 散列(Hashing)是计算机科学中一种对资料的处理方法,通过某种特定的函数/算法(称为散列函数/算法)将要检索的项与用来检索的索引(称为散列,或者散列值)关联起来,生成一种便于搜索的数据结构( ...

  7. 哈希查找解决地址冲突的两种最常见方法(线性探测再散列,链地址法)C++实现

    哈希查找解决地址冲突的两种最常见方法(线性探测再散列,链地址法)C++实现 参考文章: (1)哈希查找解决地址冲突的两种最常见方法(线性探测再散列,链地址法)C++实现 (2)https://www. ...

  8. PAT甲级1145 Hashing - Average Search Time:[C++题解]哈希表、哈希表开放寻址法、二次探测法、求平均查找次数

    文章目录 题目分析 题目链接 题目分析 来源:acwing 本题的分析见另一道PAT的题目:PAT甲级1078 Hashing:[C++题解]哈希表.哈希表开放寻址法.二次探测法链接的题目就是让建立h ...

  9. PAT甲级1078 Hashing:[C++题解]哈希表、哈希表开放寻址法、二次探测法

    文章目录 题目分析 题目链接 题目分析 来源:acwing 分析: 本题建立hash表是利用只具有正增量的二次探测法来解决冲突, 索引 = 数 % 哈希表的大小 如果映射到同一个索引idx,hash表 ...

最新文章

  1. FCES2021|一起来探讨“开源文化与计算机教育”,7月16日常州见
  2. Xcode7 项目转 Xcode6 时 出现问题
  3. 用matlab画大数据曲线_基于MATLAB的大数据分析
  4. 王道计算机网络 传输层整理 超详细版
  5. python winsound_将winsound转换为linux平台?
  6. mediawiki 编辑php代码,mediawiki_1.25配置wikieditor编辑器
  7. Ubuntu学习日记--Lesson7:文件权限管理chmod
  8. extern关键字的作用_一文搞懂static关键字与链接性
  9. 【愚公系列】2022年04月 微信小程序-Flex布局详解
  10. Spring boot项目启动报无法加载主类
  11. 360来硬拼,云盘免费用了!新注册就能获得36T容量!
  12. 相关性分析及SPSS软件操作
  13. CAS单点登录:CAS客户端搭建(整合Shiro和Spring Boot)
  14. KendoUI系列:switch 开关
  15. L1-049 天梯赛座位分配 (20分) (C++)
  16. Seq2Seq and NMT(基于cs224n的最全综述神经机器翻译技术)
  17. 一份写给极客的智能家居指南
  18. 绘图杂记【3】Python / R语言 制作峰峦图
  19. 打破双亲委派的几种方式
  20. 【Gym - 100482B Farmer 】 思维

热门文章

  1. java wifidog_java4wifidog_server_README
  2. 在线查看visio文档(.vsdx) 不用破解Visio的好方法:)
  3. 防蓝光护眼灯哪个牌子比较好?目前比较好用的护眼灯推荐
  4. java运行cmd命令
  5. 氧化硅湿蚀刻中枝晶状缺陷的去除方法
  6. Android:时间轴动态记录安排事情,24小时制,按照时间先后排序
  7. 再谈Comparator
  8. 模式匹配Pattern Matching
  9. 玩转ChatGPT:中科院ChatGPT Academic项目部署与测评
  10. Python爬虫实战之一 - 基于Requests爬取拉勾网招聘信息,并保存至本地csv文件