【数据结构笔记40】哈希表冲突处理方法:开放地址法(线性探测、平方探测、双散列、再散列),分离链接法
本次笔记内容:
11.3.1 开放定址法
11.3.2 线性探测
11.3.3 线性探测-字符串的例子
11.3.4 平方探测法
11.3.5 平方探测的实现
11.3.6 分离链接法
文章目录
- 冲突处理方法常见思路
- 散列表查找性能分析
- 开放地址法(Open Addressing)
- 线性探测法(Linear Probing)
- 线性函数:字符串例子
- 平方探测法(Quadratic Probing)
- 平方探测法的实现
- 双散列探测法(Double Hashing)
- 再散列(Rehashing)
- 分离链接法(Separate Chaining)
冲突处理方法常见思路
- 换个位置:开放地址法;
- 同一位置的冲突对象组织在一起:链地址法。
散列表查找性能分析
- 成功平均查找长度(ASLs)
- 不成功平均查找长度(ASLu)
比如:
如上图,ASLs即找每个元素的平均查找次数,即数字11需要查找1次(1+0次冲突次数);数字30需要查找7次(1+6次冲突次数)。
ASLu即,对于不存在的元素,如22、33(其余数为0),需要查找3次(3次后移动到空位上),才能确认不存在。对每一类余数加和求平均。
开放地址法(Open Addressing)
一旦产生了冲突(改地址已有其他元素),就按某种规则取寻找另一空地址。
若发生了第i次冲突,试探的下一个地址将增加did_idi,基本公式是hi(key)=(h(key)+di)modTableSize(1≤i<TableSize)h_i(key)=(h(key)+d_i) \; mod \; TableSize (1 \le i < TableSize)hi(key)=(h(key)+di)modTableSize(1≤i<TableSize)
其中,did_idi决定了不同的解决冲突方案:线性探测(di=id_i=idi=i)、平方探测(di±i2d_i \pm i^2di±i2)、双散列(di=i×h2(key)d_i=i \times h_2(key)di=i×h2(key))。
线性探测法(Linear Probing)
线性探测法:以增量序列1,2,…,(TableSize-1)循环试探下一个存储地址。
示例如下。
题目如上图。
如上图,线性探测会形成“聚集现象”。即数都聚在一起了。
线性函数:字符串例子
例子如上图,很简单。
平方探测法(Quadratic Probing)
平方探测法:以增量序列12,−12,22,−22,...,q2,−q21^2,-1^2,2^2,-2^2,...,q^2,-q^212,−12,22,−22,...,q2,−q2且q≤⌊TableSize/2⌋q \le \lfloor TableSize / 2 \rfloorq≤⌊TableSize/2⌋循环试探下一个存储地址。
此例子与线性探测所举相同。
如上图其ASLs在此例中好于线性探测2.56。
但是二次探测存在一个问题,即:在有空间的情况下,也不一定能找到空间。
如上图,上面展示了一个通过二次探测却无法找到空间的例子。
但是,有定理:如果散列表长度TableSize是某个4k+3(k是正整数)形式的素数时,平方探测法就可以探查到整个散列表空间。
平方探测法的实现
typedef struct HashTbl *HashTable;
struct HashTbl
{int TableSize;Cell *TheCells;
} H;HashTable InitializeTable(int TableSize)
{HashTable H;int i;if (TableSize < MinTableSize){Error("散列表太小");return NULL;}// 分配散列表H = (HashTable)malloc(sizeof(struct HashTbl));if (H == NULL)FatalError("空间溢出!!!");H->TableSize = NextPrime(TableSize); // 找一个素数// 分配散列表 CellsH->TheCells = (Cell *)malloc(sizeof(Cell) * H->TableSize);if (H->TheCells == NULL)FatalError("空间溢出!!!");for (i = 0; i < H->TableSize; i++)H->TheCells[i].info = Empty;return H;
}
如上图,之所以留一个info变量,是因为考虑到删除操作时如果直接将元素置为空,则会产生问题,因此info可以表示“Deleted”已删除。
Position Find(ElementType Key, HashTable H)
{// 平方探测Position CurrentPos, NewPos;int CNum;CNum = 0;NewPos = CurrentPos = Hash(Key, H->TableSize);while (H->TheCells[NewPos].info != Empty &&H->TheCells[NewPos].Element != Key)){// 字符串类型的关键词需要strcmp函数if (++cNum % 2){// 判断奇偶NewPos = CurrentPos + (CNum + 1) / 2 * (CNum + 1) / 2;while (NewPos >= H->TableSize)NewPos -= H->TableSize;}else{NewPos = CurrentPos - CNum / 2 * CNum / 2;while (NewPos < 0)NewPos += H->TableSizee;}}return NewPos;
}
如上为查找新位置操作。判定Cnum奇偶是为了在did_idi与Cnum间建立起映射。
映射关系如上图所示。
void Insert(ElementType Key, Hashtable H)
{Position Pos;Pos = Find(Key, H);if (H->TheCells[Pos].info != Lengitimate){// 确认在此插入H->TheCells[Pos].info = Legitimate;H->TheCells[Pos].Element = Key;// 字符串类型的关键词需要strcpy函数}
}
插入操作如上。在开放地址散列表中,删除操作要很小心。通常只能 “懒惰删除” ,即需要增加一个 “删除标记(Deleted)” ,而并不是真正删除它。以便查找时不会 “断链” 。其空间可以在下次插入时 重用 。
双散列探测法(Double Hashing)
偏移量也通过hash映射出来。
did_idi为i∗h2(key)i*h_2(key)i∗h2(key),h2(key)h_2(key)h2(key)是另一个散列函数。探测序列成:h2(key),2h2(key),3h2(key),..h_2(key),2h_2(key),3h_2(key),..h2(key),2h2(key),3h2(key),..
- 对任意的key,h2(key)≠0h_2(key) \ne 0h2(key)=0;
- 探测序列还应该保证所有的散列存储单元都应该能够被探测到。选择以下形式有良好的效果:h2(key)=p−(keymodp)h_2(key)=p-(key \; mod \; p)h2(key)=p−(keymodp)
其中:p<TableSizep < TableSizep<TableSize,p、TableSize都是素数。
再散列(Rehashing)
- 当散列表元素太多(即装填因子α太大)时,查找效率会下降;
- 可以考虑将散列表扩大,如从11变成23;
- 当扩大后,需要重新插入元素;
- 实用最大装填因子一般取0.5≤α≤0.850.5 \le \alpha \le 0.850.5≤α≤0.85;
- 当装填因子过大时,解决的方法是加倍扩大散列表,这个过程叫做“再散列(Rehashing)”。
分离链接法(Separate Chaining)
将相应位置上冲突的所有关键词存储在同一个单链表中。
链地址法如上图。
typedef struct ListNode *Position, *List;
struct ListNode
{ElementType Element;Position Next;
};typedef struct HashTbl *HashTable;
struct HashTbl
{int TableSize;List TheLists;
};Position Find(ElementType Key, HashTable H)
{Position P;int Pos;Pos = Hash(Key, H->TableSize); // 初始散列表位置P = H->TheLists[Pos].Next; // 获得链表头while (P != NULL && strcmp(P->Element, Key))P = P->Next;return P;
}
【数据结构笔记40】哈希表冲突处理方法:开放地址法(线性探测、平方探测、双散列、再散列),分离链接法相关推荐
- 学习数据结构笔记(8) ---[哈希表(Hash table)]
B站学习传送门–>尚硅谷Java数据结构与java算法(Java数据结构与算法) 一般在java程序访问数据库时都会安排从内存的缓存层中取数据;之前的做法是自己写个哈希表,实现对数据的缓存. 哈 ...
- Python与数据结构[4] - 散列表[1] - 分离链接法的 Python 实现
分离链接法 / Separate Chain Hashing 前面完成了一个基本散列表的实现,但是还存在一个问题,当散列表插入元素冲突时,散列表将返回异常,这一问题的解决方式之一为使用链表进行元素的存 ...
- j - 数据结构实验:哈希表_一看就懂的数据结构基础「哈希表」
哈希表 哈希表(Hash table),是存储键值(Key Value)对数据的一种数据结构. 例如,我们可以将人的名字作为键,性别作为值来存储.通过把键映射到表中的一个位置来访问数据,以提高查找速度 ...
- JavaScript数据结构之 —— 08哈希表
散列算法(也就是哈希)的作用是尽可能快地在数据结构中找到一个值.在之前如果要在数据结构中获得一个值(使用get方法),需要遍历整个数据结构来找到它. 所有元素根据和该元素对应的键,保存在数组的特定位置 ...
- 数据结构实验:哈希表
数据结构实验:哈希表 题目描述 在n个数中,找出出现次数最多那个数字,并且输出出现的次数.如果有多个结果,输出数字最小的那一个. 输入 单组数据,第一行数字n(1<=n<=100000). ...
- 数据结构之哈希表的分离链接法java实现
哈希表的分离链接法 原理 Hash Table可以看作是一种特殊的数组.他的原理基本上跟数组相同,给他一个数据,经过自己设置的哈希函数变换得到一个位置,并在这个位置当中放置该数据.哦对了,他还有个名字 ...
- 哈希表处理冲突的方法?
哈希表处理冲突的方法? 一.基本概念 二.处理冲突的方法 (1) 链地址法 (2) 开放定址法 (3) 再散列法 (4) 建立一个公共溢出区 一.基本概念 哈希表,也叫散列表,是根据关键字而直接进行访 ...
- Java解决Hash(散列)冲突的四种方法--开放地址法(线性探测,二次探测,伪随机探测)、链地址法、再哈希、建立公共溢出区
Java解决Hash(散列)冲突的四种方法--开放地址法(线性探测,二次探测,伪随机探测).链地址法.再哈希.建立公共溢出区 参考文章: (1)Java解决Hash(散列)冲突的四种方法--开放地址法 ...
- 冲突处理方法----开放定址法
1 前言 常用处理冲突的思路: 换个位置: 开放地址法 同一位置的冲突对象组织在一起:链地址法 2 开放定址法(Open Addressing) 一旦产生了冲突(该地址已有其它元素),就按某种规则去寻 ...
最新文章
- nutz 结合QueryResult,Record 自定义分页查询,不构建pojo 整合
- Java创建线程的方式
- 细思极恐,你真的会写 Java 吗
- MYSQL中常用的强制性操作(例如强制索引)
- 首次公开!《阿里计算机视觉技术精选》揭秘前沿落地案例
- linux如何安装django
- Angular JS(二) 指令部分
- ActiveMQ - spring集成jms
- android 下载多个文件怎么打开,从android的下拉框中下载多个文件或文件夹?
- 函数进阶---闭包/装饰器/迭代器/生成器---高级特性
- asp.net 通过ajax方式调用webmethod方法使用自定义类传参及获取返回参数
- 从深圳到底特律——我的出国求学之路
- 百度文库复制内容,留作笔记
- android httpclient版本,Android studio使用http 没有 HttpClient
- JS 轮播图 图片切换(定时器)
- HashMap的应用
- Python中Django与Echarts的结合用法
- 《Fluent Python》学习笔记:第 8 章 对象引用、可变性和垃圾回收
- java代码实现15位身份证号码升级到18位
- 开启微信悬浮窗权限有什么用_用这一个App,干掉其他所有App的...所有广告
热门文章
- 攻防比赛_深度:一场攻防杂乱的比赛,凸显了索肖三中卫阵型的核心是谁
- 关于如何查看 EntityValidationErrors 详细信息的解决方法
- Jenkins持续发布解决方案
- IDEA使用@Data注解,类调用get、set方法标红的解决办法
- 关于System.AccessViolationException异常
- Java程序如何获得自己的进程ID?
- 如何在C#中获取Unix时间戳
- JAVA调用Web Service接口的五种方式
- activiti删除已经部署的流程定义
- php 读取mysql 二维数组_PHP操作 二维数组模拟mysql函数