前言:继续愉快数据结构与算法blog。
1、散列表(哈希表)查找
1)要在a[]中查找key关键字的记录:
----顺序表查找:挨个儿比较来找
----有序表查找:二分法查找
----散列表查找:记录的存储位置=f(关键字),从而开始找
补充小知识,散列表是根据关键码值(Key value)而直接进行访问的数据结构。(源自百度解析)
2)散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应的一个存储位置f(key)。这里将这种对应关系f称为散列函数,又称为哈希(hash)函数。采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间成为散列表或哈希表(hash table)
3)散列表的查找步骤:
当存储记录时,通过散列函数计算出记录的散列地址;
当查找记录时,通过同样的散列函数计算记录的散列地址,并按此散列地址访问该记录
4)散列表不适用于关键字为范围的,如查找22-18岁的女同学,这种关键字就不行;同样不适用于关键字范围过大的,如找一个班里面的某一位男同学,设置关键字男同学,那么只能得到班全部男同学,而不能得到所需要的具体男生。
5)散列表对于1对1映射关系的查找效率比较高
6)设计散列函数时,key要求不同,并且对应的函数值也是不相同的
2、散列函数的构造方法
1)构造散列函数的两个基本原则
----计算简单
----分布均匀
2)散列函数构造方法:
----直接定址法:取关键字的某个线性函数值为散列地址,即:f(key)=a*key+b
----数字分析法:数字分析法通常适合处理关键字位数比较大的情况。
例如,存储某家公司员工登记表,如果用手机号作为关键字,可以发现抽取手机号后四位作为散列地址是不错的选择。
【HLR识别码,表示的是号码归属地】
----平均取中法,将关键字平方之后取中间若干位数字作为散列地址。其适用于关键字分布未知,位数不大情况。
----折叠法,将关键字从左到右分割成位数相等的几部分,然后将这几部分叠加求和,并按散列表表长取后几位作为散列地址
----除留余数法,对散列表长为m的散列函数计算公式:
-------- f(key)=key mod p(p<=m)
该方法时最常用的构造散数列函数方法
除留余数法,不仅可以对关键字直接取模,也可以通过折叠、平方取中后再取模。
当然,p(被除数)选取很重要,如果对于下列表的关键字,p还是选取12的话,会导致冲突问题的出现:
根据经验,p选取时,p值应该小于或等于表长。
----随机数法,选取一个随机数,取关键字的随机函数值为它的散列地址
即:f(key)=random(key)
这里的random是随机函数,当关键字的长度不等时,采用这个方法构造散列函数是比较合适的
3)构造散列函数时选取方法,可考虑从以下方面进行选择:
----计算散列地址所需的时间
----关键字的长度
----散列表的大小
----关键字的分布情况
----记录查找频率
3、处理散列(哈希)冲突的方法
1)开放地址法,一旦发生冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列表地址总能找到,并将记录存入(线性探测法)
如果位置有人占了,就重新选择一个di,直到找到的空位置为止。
可以使用di的取值方式,例如使用平方运算来尽量解决堆积问题(二次探测法):
----- fi(key)=(f(key)+di) MOD m (di=1^ 2,-1^ 2,2^2 , -2 ^ 2 … , q ^ 2 , -q ^ 2,q<=m/2)
还有一种方法是,在冲突时,对于位移量di采用随机函数计算得到,称之为随机探测法:
-fi(key)=(f(key)+di) MOD m (di是一个随机函数获取的数列)
2)再散列函数法
使用多个散列函数,一个产生冲突时,可以调用其他个散列函数
3)链地址法
【有冲突的,就链接在现有的后面,如12链接在48后面】
4)公共溢出区法
【先在基本表中存放,冲突的存放在溢出表中,查找时,先基本表后溢出表】
4、散列表查找的代码实现
散列表的元素插入和查找都是使用开发地址法中的线性探测
//210205
#define HASHSIZE 12
#define NULLKEY -32768
#include <string>
typedef struct {
int* elem; //存放数据元素的基址,动态分配数组
int count; //当前数据元素的个数
}HashTable;
int InitHashTable(HashTable* H) {
H->count = HASHSIZE;
H->elem = (int*)malloc(HASHSIZE * sizeof(int));
if (!H->elem) {
return -1;
}
for (int i = 0; i < HASHSIZE; i++) {
H->elem[i] = NULLKEY;
}
return 0;
}
//使用除留余数法
int Hash(int key) {
return key % HASHSIZE;
}
//插入关键字到散列表
void InsertHash(HashTable* H, int key) {
int addr;
addr = Hash(key);
while (H->elem[addr]!=NULLKEY) //如果不为空,则冲突出现
{ //这里采用的是开发地址法里面的线性探测法
addr = (addr + 1) % HASHSIZE;
}
H->elem[addr] = key; //找到空位之后,插入
}
//散列表中查找关键字
int SearchHash(HashTable H, int key, int* addr) {
*addr = Hash(key);
//当发生冲突时
while (H.elem[*addr]!=key)
{
//查找发现冲突时,采用开发地址法中的线性探测
*addr = (*addr + 1) % HASHSIZE;
if (H.elem[*addr] == NULLKEY || *addr == Hash(key)) {//空位置,或者循环到原点,则所查找点表示不存在
return -1;
}
}
return 0;
}
//一般理解
#########################
不积硅步,无以至千里
好记性不如烂笔头
感谢小甲鱼老师
截图权利归原作者所有