哈希查找/散列查找(HashSearch)

哈希表/散列表:

1.什么是哈希表:

  • 哈希表不同于线性表数表之处在于其查找关键字(key)时不需要遍历表哈希表中的每一个元素都可以根据元素值计算出其存放的地址,从而达到查找时长为O(1)。
  • 这就好比" 字典",我们查找字典中的某个关键字(key)时,只需要查找目录即可找到该关键字(key)存放的页码(位置/地址),而查找的目录是有限的,且是线性的(常数阶/线性阶),所以无论字典存放的元素、关键字再多,我们也能通过目录快速定位查找到某个元素。
  • python中的 “字典(dict)” 就是如此,利用哈希表的存储结构。

2.基本概念和术语:

  1. 散列表:有限的、连续的存储空间,一般是数组。
  2. 散列函数:散列函数是函数,这个函数构建了与关键字(key)一 一对应的地址(address),不同的散列方法需要构造不同的散列函数,Hash(key)就是散列函数。
  3. 散列地址:由散列函数的到的地址就是散列地址,address=Hash(key)
  4. 冲突:key1≠key2; Hash(key1)==Hash(key2),即两个不同关键字,具有相同地址时,就叫发生冲突。
  5. 同义词:key1==key2,则key1key2互称为同义词。

散列法与散列函数的构造

1.散列函数的要求:

  1. 每一个 关键字(key) 只能有一个 地址(address) 与之对应。
  2. 函数值域必须在表长范围之内。
  3. 散列地址尽量均匀分布,尽可能的减少冲突。

2.散列法:

  1. 数字分析法:参考<数据结构p221>
  2. 平均取中法:参考<数据结构p222>
  3. 折叠法:参考<数据结构p222>
  4. 直接地址法:Hash(key)=keyHash(key)=a·kay+b
  5. 除留余数法:取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地址。Hash(key)=key % P

处理冲突的方法:

1.开放地址法(闭散列法):

  • 核心思想是,把发生冲突的元素放到哈希表中的另外一个位置。
  1. 线性探测法:发生冲突时,逐位往后挪动,寻找合适位置,只要哈希表没满,就一定能找到一个不发生冲突的位置。
    addressi=( Hash(key) + di ),其中 di = 1,2,3···
关键字序列:(44、35、14、54)

散列函数: 
H(Key) = key MOD 10

处理冲突:线性探测

  1. 二次探测法:发生冲突时,每次向后挪动k2个单位(k为挪动次数)。
    addressi=( Hash(key) + di ),其中 di = 12,22,32···
关键字序列:(44、35、14、54)

散列函数: 
H(Key) = key MOD 10

处理冲突:二次探测

  1. 伪随机探测法:发生冲突时,每次向后挪动k个单位(k为伪随机生成数)。
  • 线性探测法的优点是:只要散列表未填满,总能找到一个不发生冲突的地址。缺点是:会产生 ”二次聚集“ 现象。而二次探测法和伪随机探测法的优点是:可以避免 “二次聚集“ 现象。缺点也很显然:不能保证一定找到不发生冲突的地址。

2.链地址法(开散列法):

  • 链地址法的基本思想是:把具有相同散列地址的记录放在同一个单链表中,称为同义词链表。有 m个散列地址就有 m个单链表,同时用数组 HT[0…m-1]存放各个链表的头指针,凡是散列地址为 i 的记录都以结点方式插入到以HT[i]为头结点的单链表中。
    在这里插入图片描述

散列表的平均查找长度(ASL):

1. 闭散列(开放地址):

关键字序列:(7、8、30、11、18、9、14)

散列函数: 
H(Key) = (key x 3) MOD 7

装载因子: 
0.7

处理冲突:线性探测
  • 数据7个,填装因子0.7,所以散列表长度=7/0.7=10,即数组下标0~9

① 查找成功:

  • 查找情况:
第一个元素7 -> 0 ;              (查找1次)
第二个元素8 -> 3 ;              (查找1次)
第三个元素30 -> 6 ;             (查找1次)
第四个元素11 -> 5 ;             (查找1次)
第五个元素18 -> 5 -> 6 -> 7 ;   (查找3次)
第六个元素9 -> 6 -> 7 -> 8 ;    (查找3次)
第七个元素14 -> 0 -> 1 ;        (查找2次)
  • 有7个数据元素,故有7种情况。
  • 所以ASLsucc=(1+1+1+1+3+3+2)/ 7 = 12/7

② 查找失败:

  1. 由散列函数获取首地址之后,再探测到空位置,即查找失败
  2. 探测完整个表,仍未找到,即查找失败。
  • 比较情况:
注:add表示哈希表地址

add[0] -> add[1] -> add[2](空)                           比较3次
add[1]-> add[2](空)                                      比较2次
add[2](空)                                               比较1次
add[3] -> add[4](空)                                     比较2次
add[4] (空)                                              比较1次
add[5] -> add[6] -> add[7] -> add[8] -> add[9](空)       比较5次
add[6] -> add[7] -> add[8] -> add[9](空)                 比较4次

只需看add[0] ~ add[6]
无需看add[7] ~ add[9]
  • 为什么只用看add[0] ~ add[6]
  1. 因为散列函数Hash(key)=(key x 3) % 7,中有个mod7,所以由散列函数求出来的初地址只能是0~6 ,故只有7种情况。
  • 所以ASLunsucc=(3+2+1+2+1+5+4)/ 7 = 18/7

2.开散列(链地址):

  • 计算ASL的方法与开散列类似。
关键字序列:(19、14、23、1、68、20、84、27、55、11、10、79)

散列函数: 
H(Key) = key MOD 13

处理冲突:链地址法

① 查找成功:

  • 每个单链表中的第 1 个结点的关键字(如 14、 68、 19、 20、 23、 11), 查找成功时只需比较 1 次,而对千第 2个结点的关键字(如 1、 55、 84、 10), 查找成功时需比较 2 次, 第 3 个结点的关键字 27 需比较 3 次, 第4个结点的关键字 79 则需比较4次才能查找成功。这时,查找成功时的平均查找长度为:ASLsucc=(1·6 + 2·4+3+4)/ 12

② 查找失败:

  • 采用链地址法处理冲突时,待查的关键字不在表中,若计算散列函数 H(key)= 0, HT[0]的指 针域为空,比较1 次即确定查找失败。若H(key)= 1, HT[l]所指的单链表包括4个结点,所以需要比较5次才能确定失败。类似地, 对H(key)= 2, 3, …,12 进行分析,可得查找失败的平均查找 长度为:ASLunsucc= (1+5 +1+3 +1+1+3+2+1+1+3+2+1)/ 13

3.ASL计算总结:

① 查找成功:

② 查找失败:


完整源代码

#include <stdio.h>
#include <stdlib.h>

#define dataType int
#define HashSize 12
#define Empty -32767

typedef struct
{
	dataType *value;
	int hashsize;
}HashMap; 

void IntiHashMap(HashMap *H)
{
	int i;
	
	H->hashsize=HashSize; //初始化哈希表长度 
	H->value=(dataType *)malloc(HashSize*sizeof(dataType)); //创建哈希表数组 
	for(i=0;i<HashSize;i++) //初始化哈希表为空 
	{
		H->value[i]=Empty;
	} 
}

//散列函数 (除留余数法) 
int Hash(dataType value) 
{
	return value%HashSize; //除数也可换成其他更合适的值,如小于HashSize的最大质数 
}

void Insert(HashMap *H,int data)
{
	int InsertAddress;
	
	InsertAddress=Hash(data);
	
	//发生冲突 
	while(H->value[InsertAddress]!=Empty)//线性探测 
	{
		InsertAddress=(InsertAddress+1)%HashSize;
	}
	H->value[InsertAddress]=data;
} 

int Search(HashMap *H,int SeartData)
{
	int HashAddress;
	
	HashAddress=Hash(SeartData);
	
	while(H->value[HashAddress]!=SeartData)
	{
		HashAddress=(HashAddress+1)%HashSize;
		
		if(H->value[HashAddress]==Empty||HashAddress==Hash(SeartData))
		{
			return -1;
		}
	}
	
	return HashAddress;
}


int main() 
{
	HashMap H;
	int i;
	int a[12]={12,67,56,16,25,37,22,29,15,47,48,34};

	IntiHashMap(&H); 
	
	for(i=0;i<HashSize;i++)
	{
		Insert(&H,a[i]);
	}
	
	printf("address   ");
	for(i=0;i<HashSize;i++)
	printf("%d\t",i);
	printf("\n\n");
	
	printf("a[i]     ");
	for(i=0;i<HashSize;i++)
	printf("%d\t",a[i]);
	printf("\n\n");
	
	printf("Hash[i]  ");
	for(i=0;i<HashSize;i++)
	printf("%d\t",H.value[i]);
	printf("\n\n");
	
	printf("a[6]的地址是:");
	printf(" %d",Search(&H,a[6]));
	
	return 0;
}

执行结果:

address  0  1  2  3  4  5  6  7  8  9  10 11

a[i]     12 67 56 16 25 37 22 29 15 47 48 34

Hash[i]  12 25 37 15 16 29 48 67 56 34 22 47

a[0]的地址是: 0
a[1]的地址是: 7
a[2]的地址是: 8
a[3]的地址是: 4
a[4]的地址是: 1
a[5]的地址是: 2
a[6]的地址是: 10
a[7]的地址是: 5
a[8]的地址是: 3
a[9]的地址是: 11
a[10]的地址是: 6
a[11]的地址是: 9
--------------------------------
Process exited after 0.01566 seconds with return value 0
请按任意键继续. . .

参考:

  1. 数据结构C语言第二版—严蔚敏
  2. 哈希表(散列表)详解
  3. Hash表的平均查找长度ASL计算方法
©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页