数据结构与算法:哈希

哈希

哈希/散列(Hashing):通过关键字Key映射到散列地址上,存储Value。属于查找算法

查找算法
查找的本质:给定已知对象找到位置
1、顺序查找:时间复杂度O(N)
2、二分查找:O(logN)
3、二叉搜索树O(h) h:二叉树的高度
平衡二叉树 O(logN)
4、哈希查找:几乎是常量O(1),与问题的规模无关

散列查找关键

1、如何计算元素存储的位置
必须构造散列函数确定Key存储位置
2、如何解决冲突?
不同的关键字可能会映射到同一个散列地址,即为冲突,所以必须采取某种方法结局多个关键字映射相同的问题

散列函数的构造方式

好的散列函数应该满足以下两个方面:
1、计算简单,提高Key->散列地址的转换速度
2、关键字对应地址空间分布均匀,减少冲突

直接地址法
h(key) = a × key + b,通过构造关键字的线性函数值为散列地址
映射结果
在这里插入图片描述
除留余数法
散列函数:h(key) = key mod p,一般情况下p为素数
在这里插入图片描述
数字分析法

通过分析数字关键字在各位上的变化情况,取比较随机的位作为散列地址
比如11位手机号码作为Key,一般取后4位作为地址:
散列函数:h(key) = atoi(Key+7)

11位的手机号码一般满足这样的变化规律
在这里插入图片描述18位的身份证号码就会有这样的变化规律
在这里插入图片描述所以取6、10、8、4、1、9,地址空间分布均匀,冲突的几率可能会比较小一点

折叠法
把关键词分割成位数相同的几个部分,然后叠加
789456 => 789+456 = 12345 = h(key)

平方取中法
1245 => 1245 * 1245 = 1550025 取500为h(key)

字符关键词的散列构造

ASCII码加和法
h(key) = (Σkey[i]) mod TableSize
冲突会比较严重

**前3个字符移位法 **
h(key)=(key[0]×27^2 + key[1]×27 + key[2])mod TableSize

移位法
h(key) = (Σkey(n-i-1)×32^i)mod TableSize ,据说取32是最优的方式

Index Hash ( const char *Key, int TableSize ) {
           
	unsigned int h = 0;     /* 散列函数值,初始化为0 */    
 	while ( *Key != ‘\0)  /* 位移映射 */        
	  	h = ( h << 5 ) + *Key++;     
	return  h % TableSize; 
} 

冲突处理

常见的方法:
1、开放地址法:找一个空位置存储
2、链地址法:相同位置的冲突,形成链

开放地址法

一旦产生了冲突(该地址已有其它元素),就按某 种规则去寻 找另一空地址
如果在 i 位置发生了冲突,以di的递增寻找下一个地址
hi(key) = (h(key)+di) mod TableSize
di就决定了解决冲突的偏移量
根据di的不同,冲突方案分为:
1、线性探测(di=i)
2、平方探测(di = ± i * i)
3、双散列(di = i * h’(key))

线性探测法
以增量序列 1,2,……,(TableSize -1) 循环试探下一个存储地址。

TableSize = 13
在这里插入图片描述在这里插入图片描述在这里插入图片描述成功平均查找长度(ASLs) :查找表中关键词的平均查找比较次数**(其冲突次数加1)**
不成功平均查找长度 (ASLu) :不在散列表中的关键词的平均查找次数

平方探测法(二次探测)

平方探测法:以增量序列12,-12,22,-22,……,q2,-q2 且q ≤ ⌊TableSize/2⌋ 循环试探下一个存储地址。
跟线性探测法差不多,只是增量不一样,8举例子了

双散列探测法
双散列探测法: di 为i*h2(key),h2(key)是另一个散列函数
探测序列还应该保证所有的散列存储单元都应该能够被探测到。
选择以下形式有良好的效果:
h2(key) = p - (key mod p)

再散列
解决方式:增大散列表
弊端:要重新计算已经在散列表的hash值

分离链接法

分离链接法:将相应位置上冲突的所有关键词存储在同一个单链表中
在这里插入图片描述只要把冲突的元素,按头插法插入到该位置上的链表就好了

散列表的性能分析

平均查找长度(ASL)用来度量散列表查找效率:成功、不成功
关键词的比较次数,取决于产生冲突的多少:
1、散列函数是否分配空间均匀
2、处理冲突的方法
3、散列表的装填因子(元素个数/表长)

线性探测法的查找性能

线性探测法的期望探测次数 满足下列公式:
在这里插入图片描述
平方探测法和双散列探测法的查找性能
在这里插入图片描述分离链接法的查找性能

在这里插入图片描述

期望探测次数与装填因子α的关系
在这里插入图片描述
在这里插入图片描述根据期望探测次数与装填因子α的关系,可以得到合理的的最大装入因子α应该不超过0.85。

哈希总结

1、选择合适的 h(key) ,散列法的查找效率期望是常数O(1),它几乎与关键字的空间的大小n无关!
2、散列方法是一个以空间换时间,所以a应该较小
3、散列方法的存储对关键字是随机的,不便于顺序查找关键字,也不适合于范围查找,或最大值最小值查找。

开放地址法:
1、优点:本质上是一个数组,存储效率高,方便查找
2、缺点:散列表易发生聚集

分离链法:
1、散列表是顺序存储和链式存储的结合,链表部分的存储效率和查找效率都比较低,容易解决冲突
2、弊端:太小的α可能导致空间浪费,大的α又将付出更多的时间代价。不均匀的链表长度导致时间效率的严重下降。

开放地址法和分离链接法的代码实现

平方探测

#include<iostream>
#include<cstdio>
#include<math.h>
#include<cstring>
using namespace std;
//设置最大的散列表长度
#define MAXTABLESIZE 100000
typedef int ElementType;
//存储散列地址的类型
typedef int Index;
//数据的位置与地址类型保持一致
typedef Index Position;
//设置存储单元的状态:已有元素、空单元、该元素被删除
typedef enum{
   
	Legitimate,
	Empty,
	Deleted
}EntryType;
//存储单元的指针类型
typedef struct HashEntry *Cell;
//存储单元结点
struct HashEntry{
   
	//存储结点数据
	ElementType data;
	//存储结点状态
	ElementType Info;
};
//散列表的类型
typedef struct TblNode *HashTable;
//散列表的结点定义
struct TblNode{
   
	//表的最大长度
	int tableSize;
	//存储单元
	Cell cell; 
};
//计算出比N大的最小素数
int 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值