数据结构——哈希表的基础

一、顺序表.链表等等查找的局限性:

数据和存储之间没有关系,在查找一个数据时只能从前向后一个一个的进行数据对比,所以查找函数的效率是O(n)。

二、哈希函数

所以理想情况下:我们希望让查找函数的时间复杂度达到O(1),让数据和其存储位置之间产生某种函数关系(映射关系),这种函数关系就是哈希函数,格式:存储位置=f(关键字)

散列(哈希)技术:可以通过查找关键字不需要比较就可以获得需要记录的存储位置,散列技术是在记录的存储位置和他的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置,查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上,我们将这种对应关系f称为散列函数,又称为哈希函数,按照这个思想,采用散列技术将记录存储在一块连续的存储空间,这块连续存储空间称为散列表或者哈希表,那么关键字对应的记录存储位置我们称为散列地址。散列技术既是一种存储方法,也是一种查找方法。

6种哈希函数的构造方法(不重要):

1.除留余数法(最常用)

f(x)=x%小于等于格子数量(但一般我们直接取余格子数量)得到的结果就是我们最终的存储下标

例如:如果我们要查找66是否存在 那么我们只需要用66%10=6在6号下标找是否值存在 大大锁一下的时间复杂度。

2.折叠法

3.平方取中法

4.数字分析法:对传进来的数字进行分析看用那一部分进行

5.直接地址法:f(x)=ax+b

6.随机数法:尾随机

哈希冲突:我们有时会碰到两个不相等的数据 key1、key2,此时也就是哈希函数中的关键字,但却同时映射到同一块存储地址中,f(key1)=f(key2)。这种现象我们叫做哈希冲突。通过精心设计的哈希函数尽量让冲突的少。

解决哈希冲突的4种方法:

1.开放地址法:

就是一旦发生了冲突,就去寻找下一个空的散列地址,只要列表足够大,空的散列地址总能找到,并将记录存入。

(1)线性探测法:

fi(key)=(f(key)+Di)%m(di=1,2,3,.....)--->f(key)意思就是如果此时计算得出的存储位置已经有数据存放我们就一个一个格子寻找所以Di是从1开始依次向后寻找空的格子,%m的原因就是雷同循环队列让他从尾跳到头防止越界。

(2)哈希堆积:

本来都不是同义词也就是两个关键字在哈希函数计算得到的地址本不相同,但是因为第一个关键字计算得到的地址已经有存放的数据就需要根据开放地址法找到空余格子进行数据存储,但第二个关键字是根据哈希函数计算得到该地址,却发现该格子被占用,这种本没有冲突,却抢占一个格子的现象叫做哈希堆积。

解决哈希堆积:

二次探测法:

一旦出现冲突,探测下一个空闲格子不是朝着一个方向,不是单单朝着一个方向,并且每次探测的幅度也是大幅变大(平方的程度来变大)。

fi(key)=(f(key)+Di)%m(Di=1^2,-1^2,2^2,-2^2.........)

目的:增加平方运算为了不让关键字都聚集在某一块区域

随机探测法(尾随机):fi(key)=(f(key)+Di)%m(di随机函数得到)

2.再散列(哈希)函数法:

只要我准备的哈希函数提供的足够多,一旦发生哈希冲突,就立马替换哈希函数,相信总会有一个可以把冲突解决掉,这种方法就使得关键字不产生聚集。另外替换是在发生哈希冲突的当时位置开始替换,并不是从头替换,而是以一种哈希函数接力的方式来进行存储。

3.公共区溢出法:

通过哈希函数的计算得到的哈希地址,先去与基本表来对比,如果等于则说明查找成功,如果不等于,则在对其溢出表进行顺序查找。

适用场景:冲突的数据很少的情况。

4.链地址法:

链地址法对于可能会造成很多冲突的哈希函数来说,提供了绝不会出现找不到地址的保障。当然,这也就带来了查找时需要遍历单链表的性能损耗。

蓝色表示有效节点。

橙色表示辅助节点。

代码设计:

(1)结构体设计:

①链地址法对应的单链表的有效节点结构体设计

cs 复制代码
typedef struct LA_Node
{
	int data;
	struct LA_Node* next;
}LA_Node;

②链地址法的表头

cs 复制代码
typedef struct LinkAddress
{
	LA_Node LA_arr[MAXSIZE];
}LinkAddress;

(2)基本函数

①计算哈希地址函数

cs 复制代码
int Get_HashAddress(int key) {
	return key % MAXSIZE;
}

②初始化

cs 复制代码
void Init_LA(LinkAddress* pla) {
for (int i = 0;i < MAXSIZE;i++) {
		//pla->LA-arr[i].data;//数据域不用赋值
		pla->LA_arr[i].next = NULL;
	}
}

③插入

cs 复制代码
bool Insert_LA(LinkAddress* pla, int key) {
	int index = Get_HashAddress(key);
	LA_Node* p = Search_LA(pla, key);
	if (p != NULL) {
		return true;
	}

	LA_Node* pnewnode = (LA_Node*)malloc(sizeof(LA_Node));
	if (pnewnode == NULL) {
		exit(EXIT_FAILURE);
	}
	pnewnode->data = key;
	pnewnode->next = pla->LA_arr[index].next;
	pla->LA_arr[index].next = pnewnode;
	return true;
}

④查找

cs 复制代码
LA_Node* Search_LA(LinkAddress* pla, int key) {
	int index = Get_HashAddress(key);

	for (LA_Node* p = pla->LA_arr[index].next;p != NULL;p = p->next) {
		if (p->data == key) {
			return p;
		}
	}
	return NULL;
}

⑤打印

cs 复制代码
void Show(LinkAddress* pla) {
	for (int i = 0;i < MAXSIZE;i++) {
		for (LA_Node* p = pla->LA_arr[i].next;p != NULL;p = p->next) {
			printf("%d->", p->data);
		}
		printf("\n");
	}
}

⑥删除

cs 复制代码
bool Delete_LA(LinkAddress* pla, int key)
{
	int index = Get_HashAddress(key);

	LA_Node* q = Search_LA(pla, key);
	if (NULL == q)
		return false;

	LA_Node* p = &pla->LA_arr[index];
	for (; p->next != q; p = p->next);

	p->next = q->next;
	free(q);
	q = NULL;

	return true;
}
相关推荐
QN1幻化引擎6 小时前
给 AI 做一次「意识体检」——基于 QN1 幻化引擎的灵鉴意识识别框架与 DalinX V5 实测
大数据·数据结构·人工智能·算法·架构
拂拉氏7 小时前
【知识讲解】 AVL树从基本成员的介绍到核心接口的实现(插入、判断、删除等等)
数据结构·算法·avl树
码少女9 小时前
数据结构——希尔排序
数据结构·排序算法
星子yu10 小时前
【学习】怎么学好数据结构
数据结构·学习
码少女12 小时前
数据结构——直接插入排序
数据结构
chh56312 小时前
C++--list
开发语言·数据结构·c++·学习·算法·list
东华万里13 小时前
第36篇: 数据结构核心:栈与队列深度解析
c语言·数据结构·算法·大学生专区
Database_Cool_13 小时前
实时计数器/排行榜首选:阿里云 Tair 高并发数据结构实践
数据结构·数据库·阿里云·云计算
变量未定义~14 小时前
ST表-龙骑士军团【算法赛】
数据结构·算法