1.3.1、哈希查找-线性探测法
资讯网址:www.qghkt.com
腾讯课堂:https://qghkt.ke.qq.com/20个常用算法
根据设定的哈希函数Hash(key)和处理冲突的方法,将一组关键码映射到一个有限的连续的地址集(区间)上,并以关键码在地址集中的“像”作为记录在表中的存储位置,这种表称为哈希表,这一映射过程称为哈希造表或散列,所得的存储位置称为哈希地址或散列地址。
对于某个哈希函数Hash和两个关键码K1和K2,如果K1≠K2而Hash(K1)=Hash(K2),则称为出现了冲突,对该哈希函数来说,K1和K2称为同义词。
一般情况下,冲突不能完全避免,只能尽可能地去减少冲突,因此在建造哈希表时不仅要设定一个“好”的哈希函数,而且要设定处理冲突的方法。
其中,Hash(key)为哈希函数;m为哈希表的表长;为增量序列。
常见的增量序列有如下3种:
① =1,2,3,…,m-1,称为线性探测再散列
② =,,,…,(k),称为二次探测再散列
③ =伪随机序列,称为随机探测再散列。
1)线性探测法
【基本思路】
1.1造哈希表
某记录的关键码为key,哈希函数值H(key)=j。若在哈希地址j发生了冲突(即此位置已存放了其他元素),则对哈希地址j+1进行探测,若仍然有冲突,再对地址j+2进行探测,依此类推,直到找到哈希表中的一个空单元并将元素存入表中为止。
1.2查找
a) 先根据哈希函数计算出元素的哈希地址
b) 若是空单元,说明查找不成功,可结束查找;若不是空单元,则与该单元中的元素进行比较。
c) 若相等,则查找成功并结束查找,否则,计算出下一个哈希地址,转b)。
【图解过程】
设关键码序列为47,34,19,12,52,38,33,57,63,21,哈希表表长为13,哈希函数为Hash(key) = key mod 11。
各关键码的哈希地址:
Hash(47) = 47 mod11 = 3, Hash(34) = 34mod 11 = 1,
Hash(19) = 19 mod11 = 8, Hash(12) = 12mod 11 = 1,
Hash(52) = 52 mod11 = 8, Hash(38) = 38mod 11 = 5,
Hash(33) = 33 mod11 = 0, Hash(57) = 57mod 11 = 2,
Hash(63) = 63 mod11 = 8, Hash(21) = 21mod 11 = 10,
(a)造哈希表,开始时为空表
哈希地址 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
关键码 |
|
|
|
|
|
|
|
|
|
|
|
|
|
(b)存关键码47,哈希地址为3,在该单元处无冲突,直接插入;然后依次将关键码34和19插入。
哈希地址 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
关键码 |
|
34 |
|
47 |
|
|
|
|
19 |
|
|
|
|
(c)存关键码12,哈希地址为1,此时有冲突,探测下一个单元(即哈希地址为2的单元)。
哈希地址 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
关键码 |
|
34 |
12 |
47 |
|
|
|
|
19 |
|
|
|
|
(d)存关键码52,哈希地址为8,也有冲突,探测下一个单元(即哈希地址为9的单元)。
哈希地址 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
关键码 |
|
34 |
12 |
47 |
|
|
|
|
19 |
52 |
|
|
|
(e)存关键码38,哈希地址为5,没有冲突;存关键码33,哈希地址为0,直接插入。
哈希地址 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
关键码 |
33 |
34 |
12 |
47 |
|
38 |
|
|
19 |
52 |
|
|
|
(f)存关键码57,哈希地址为2,有冲突,探测下一个单元(即哈希地址为3的单元,仍然有冲突,再探测下一个单元,即哈希地址为4的单元)。
哈希地址 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
关键码 |
33 |
34 |
12 |
47 |
57 |
38 |
|
|
19 |
52 |
|
|
|
(g)存关键码63,哈希地址为8,有冲突,探测下一个单元,仍有冲突,再探测下一个单元(即哈希地址为10的单元)。
哈希地址 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
关键码 |
33 |
34 |
12 |
47 |
57 |
38 |
|
|
19 |
52 |
63 |
|
|
(h)存关键码21,哈希地址为10,有冲突,探测下一个单元(哈希地址为11的单元)。
哈希地址 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
关键码 |
33 |
34 |
12 |
47 |
57 |
38 |
y |
Z |
19 |
52 |
63 |
21 |
X |
线性探测法可能使第i个哈希地址的同义词存入第i+1个哈希地址,这样本应存入第i+1个哈希地址的元素变成了第i+2个哈希地址的同义词,……,因此,可能出现很多元素在相邻的哈希地址上“聚集”起来的现象,大大降低了查找效率。为此,可采用二次探测法或随机探测再散列法,以降低“聚集”现象。
【适用场景】
依据记录的关键码直接得到其对应的存储位置,即要求记录的关键码与其存储位置之间存在一一对应关系,从而快速地找到记录。
【算法代码】
/****************************************************************
* 函数名称:createHashTable
* 功能描述:造线性探测法的哈希表
* 参数说明:hashArr, 为造好的哈希表
* hashLen,哈希表的表长
* keyArr,原关键码的序列
* keyLen,原关键码的序列的长度
* modV,哈希函数的
* 返回 值:void
* 作 者:www.qghkt.com
* 创建时间:
*****************************************************************
* Copyright @ 清哥好课堂 All rightsreserved
*****************************************************************/
void createHashTable(inthashArr[], int hashLen, int keyArr[], int keyLen, int modV)
{
//初始化 -1表示当前位置为空
int i;
for ( i = 0; i < hashLen; i++)
{
hashArr[i] = -1;
}
//造哈希表
for ( i = 0; i < keyLen; i++)
{
int m = keyArr[i];
int hashAdd = m % modV;
while(hashAdd < hashLen)
{
if (hashArr[hashAdd] ==-1)//是否为空
{
hashArr[hashAdd] =m;
break;
}
++hashAdd;
//有可能要造循环的哈希表,即最后的位置被占用,从0位置开始重新探测
if (hashAdd == hashLen)
{//一定时哈希表的表长,大于等于keyArr的长度
hashAdd = 0;
}
}
}
}
/****************************************************************
* 函数名称:hashSearch
* 功能描述:线性探测法的哈希查找
* 参数说明:hashArr, 哈希表
* hashLen,哈希表的表长
* value,所要查找的值
* modV,哈希函数
* 返回 值:如果不存在,返回-1,如果存在,则返回所在哈希表中的位置
* 作 者:www.qghkt.com
* 创建时间:
*****************************************************************
* Copyright @ 清哥好课堂 All rightsreserved
*****************************************************************/
int hashSearch(inthashArr[], int hashLen, int value, int modV)
{
int hashAdd = value % modV;
int bj = hashAdd; //是用来控制只找一圈
//说明:原本的关键序列的长度和哈希表的长度是相等,即哈希表是全面存满了元素。
while (hashArr[hashAdd] != -1)
{
if (hashArr[hashAdd] == value)
{
return hashAdd;
}
//如果不相等,则往后进行探测
++hashAdd;
//判断比过一圈,还没有没有找
if (bj == hashAdd)
{
break;
}
//处理的是,聚集在表的后面,最后一个也被填满了,但不是我们要找的值
//从头又开始找
if (hashAdd == hashLen)
{
hashAdd = 0;
}
}
return -1;
}