当前位置: 首页 > news >正文

数据结构——哈希技术及链地址法

目录

一、哈希的定义

二、哈希冲突定义

 三、构造哈希函数的方法

四、四种解决哈希冲突的方法

4.1 开放地址法

4.2 链地址法

4.3 再散列函数法

4.4 公共区溢出法

五、链地址法结构体设计

六、基本操作的实现

6.1 哈希函数

6.2 初始化

6.3 插入值

6.4 删除值

6.5 查找值

6.6 打印

6.7 测试用例 

一、哈希的定义

哈希(Hash)是一种将任意长度的输入数据通过哈希算法转换成固定长度(通常是固定长度的字符串)输出的过程。哈希算法通常会将输入数据映射为一个固定长度的字符串,这个字符串通常称为哈希值或摘要。

也就是说,我们只需要通过某个函数f ,使得存储位置=f(关键字),那么我们可以通过查找关键字不需要比较就可以获得需要记录的存储位置。

哈希函数具有以下特点:

  1. 输入数据相同,输出的哈希值必定相同。
  2. 不同的输入数据,哈希值是独立的,即不会有冲突。
  3. 哈希值的长度是固定的,不会随输入数据的长度变化而变化。
  4. 哈希值是不可逆的,即无法从哈希值还原出原始的输入数据。

哈希既是一种存储方法,也是一种查找方法。

二、哈希冲突定义

两个或多个关键码key1!=key2,但是通过哈希函数的计算,得出的结果却相等,这种现象就是发生了哈希冲突。

例如:f(x)=x %10

例如上述的86和66,计算得出都应该存放在6号下标,冲突了。 

 三、构造哈希函数的方法

构造哈希函数的方法有很多种。其中一种常见的方法是利用数学运算来将输入数据映射到固定大小的哈希值。

以下是一些构造哈希函数的方法:

  1. 直接寻址法:将输入数据直接作为索引值,获取对应的哈希值。

  2. 除留余数法:将输入数据除以一个数,取余数作为哈希值。

  3. 平方取中法:对输入数据进行平方运算,然后取中间几位作为哈希值。

  4. 折叠法:将输入数据分割成固定长度的片段,对每个片段进行加法或异或运算,最终得到哈希值。

  5. 随机哈希函数:使用随机数生成器生成哈希函数,将输入数据与随机数进行运算来得到哈希值。

  6. 加法哈希函数:将输入数据中的每个字符转换成ASCII码,然后求和得到哈希值。

  7. 乘法哈希函数:将输入数据乘以一个常数,取乘积的某几位作为哈希值。

四、四种解决哈希冲突的方法

4.1 开放地址法

         线性探测法:

     但是这种方法会存在一种情况:两个值本来都不是同义词却需要争夺一个地址,这种现象叫做堆积 

     二次探测法:

所以我们想着再探测的时候,尽可能的即向左探测,也向右探测,并且探测的幅度还在呈指数爆炸的趋势增加。这种方法叫做二次探测法

      随机探测法: 

4.2 链地址法

当哈希表用链地址法处理冲突时,每个槽位都存储一个链表或其他数据结构,该链表用于存储哈希值相同的键值对。当需要查找、插入或删除一个键值对时,首先计算对应的哈希值,然后根据哈希值找到对应的槽位,最后在该槽位上的链表中进行操作。

优点:链地址法的优点是容易实现和理解,可以有效地处理哈希冲突,适用于存储大量数据的情况。

缺点:链地址法可能会浪费一定的空间用于存储链表的指针,同时在遍历链表时可能会引起缓存未命中。

4.3 再散列函数法

当发生哈希冲突时,再散列函数法会根据一个特定的规则选择另一个哈希函数,将原始的关键字重新哈希,生成一个新的哈希值。然后,再检查新的哈希值对应的槽位是否为空,如果为空则将数据插入该位置,如果不为空则继续使用再散列函数生成新的哈希值,直到找到一个合适的位置为止。

4.4 公共区溢出法

公共区溢出法与链地址法不同的是,公共区溢出法在哈希表的每个槽位中直接存储键值对,当发生哈希冲突时,会在其他空槽位中寻找可用的位置来存储冲突的键值对。

具体来说,当插入一个键值对时,如果计算得出的哈希值对应的槽位已经被占用,那么就会根据某种探测序列在哈希表中查找下一个可用的空槽位,并将键值对存储在该位置上。具体的探测序列可以是线性探测、二次探测、双重散列等。

    优点:

    1. 公共区溢出法不需要额外的数据结构来存储冲突的键值对,节省了额外的空间开销。
    2. 可以提高数据的局部性,减少缓存未命中的可能性。
    3. 插入、查找和删除操作时,不需要额外的指针操作,节省了内存。

    缺点:

    1. 当哈希表变满时,可能会增加插入操作的复杂度,可能导致性能下降。
    2. 如果探测序列选择不当,可能会导致产生大量的聚集现象,影响查找效率。
    3. 删除操作可能较为繁琐,需要标记删除的键值对。

    五、链地址法结构体设计

    链地址法中有效节点的结构体设计:1.数据域  2.指针域

    //链地址法有效节点的结构体设计
    typedef int ElemType;
    typedef struct List_Node
    {
    	ElemType data;  //数据域
    	struct List_Node* next; //指针域
    }List_Node, *PList_Node; 
    

    链地址法中辅助节点的结构体设计: 1. 数组,有INIT_SIZE个格子,每一个格子存放的是单链表的辅助节点。

    //链地址法辅助节点的结构体设计
    #define INITSIZE 12
    typedef struct List_address
    {
    	struct List_Node arr[INITSIZE];
    }List_address;

    六、基本操作的实现

    6.1 哈希函数

    哈希函数实现,就是计算给定元素的哈希值。其中,ElemType代表元素的类型,INITSIZE代表哈希表的初始大小。

    哈希函数采用了取余运算符%来计算元素的哈希值,具体操作是将给定元素val除以INITSIZE后取余数。取余运算可以将元素的值映射到一个较小的范围内,使得得到的哈希值在哈希表的合法索引范围内。

    代码实现如下:

    //哈希函数
    int Hash(ElemType val)
    {
    	return val % INITSIZE;
    }


    6.2 初始化

    通过for循环,调用单链表中的初始化函数即可完成初始化。

    //初始化
    void Init_List_Address(List_address* pla)
    {
    	for (int i = 0; i < INITSIZE; i++)
    	{
    		InitList(&pla->arr[i]);
    	}
    }


    6.3 插入值

    1. 首先,函数接受一个指向哈希表的指针pla和待插入的元素值val作为参数。

    2. 通过调用之前提到的哈希函数Hash(val)来计算元素val的哈希值,并将其赋值给index变量。

    3. 接着,通过调用malloc()函数动态分配内存,创建一个新的节点pnewnode,用于存储待插入的元素。

    4. 如果内存分配成功(即pnewnode不为NULL),则将元素值val赋给新节点的数据域data

    5. 将新节点的next指针指向哈希表中对应索引位置的链表头节点,以实现在链表头插法的方式将新节点插入到哈希表中。

    6. 最后,返回true表示插入成功。

    代码实现如下:

    //插入值
    bool Insert(List_address* pla, ElemType val)
    {
    	assert(pla != NULL);
    	int index = Hash(val);
    	Node* pnewnode = (Node*)malloc(sizeof(Node));
    	if (NULL == pnewnode)
    		return false;
    	pnewnode->data = val;
    	pnewnode->next = pla->arr[index].next;
    	pla->arr[index].next = pnewnode;
    	return true;
    }
    


    6.4 删除值

    1. 首先,函数接受一个指向哈希表的指针pla和待删除的元素值val作为参数。

    2. 通过调用哈希函数Hash(val)计算元素val的哈希值,并将其赋值给index变量。

    3. 调用Hash_List_Address_Search()函数来查找哈希表中是否存在值为val的节点,将返回的节点赋给指针 q。

    4. 如果节点qNULL,即未找到待删除的元素,则返回false表示删除失败。

    5. 如果找到了值为val的节点q,则进入循环,遍历哈希表中索引为index的链表,找到节点q的前驱节点,即节点p

    6. 在找到节点q的前驱节点p后,将前驱节点的next指针指向节点q的后继节点,实现删除节点q的操作。

    7. 通过调用free(q)释放节点q占用的内存空间,并将指针q设为NULL,避免悬空指针。

    8. 最后,返回true表示删除成功。

    代码实现如下:

    bool Delete(List_address* pla, ElemType val)
    {
    	assert(pla != NULL);
    	int index = Hash(val);
    	Node* q = Hash_List_Address_Search(pla, val);
    	if (q == NULL)
    		return false;
    	//此时代码执行到这,证明val值节点存在在index下标里面的单链表上
    	Node* p = &pla->arr[index];
    	for (; p->next != q; p = p->next);
    	//此时代码执行到这里,证明p和q都就位
    	p->next = q->next;
    	free(q);
    	q = NULL;
    	return true;
    }


    6.5 查找值

    1. 首先,函数接受一个指向哈希表的指针pla和待查找的元素值val作为参数。

    2. 通过调用哈希函数Hash(val)计算元素val的哈希值,并将其赋值给index变量。

    3. 查找哈希表中索引为index的单链表的起始节点,并将其赋给指针p。

    4. 进入循环,遍历哈希表中索引为index的链表,逐个比较节点中存储的数据值是否等于待查找的元素值val。

    5. 如果找到与待查找的元素值相等的节点,则返回该节点的指针p,表示找到了目标节点。

    6. 如果在整个链表中都没有找到与待查找的元素值相等的节点,则循环结束后,返回NULL,表示未找到目标节点。

    代码实现如下:

    struct Node* Hash_List_Address_Search(List_address* pla, ElemType val)
    {
    	assert(pla != NULL);
    	int index = Hash(val);
    	Node* p = pla->arr[index].next;
    	for (; p != NULL; p = p->next)
    	{
    		if (p->data == val)
    		{
    			return p;
    		}
    	}
    	return NULL;
    }


    6.6 打印

    void Show(List_address* pla)
    {
    	for (int i = 0; i < INITSIZE; i++)
    	{
    		printf("第%d行:", i);
    		Node* p = pla->arr[i].next;
    		for (; p != NULL;p = p->next)
    		{
    			printf("%d->", p->data);
    		}
    		printf("\n");
    	}
    }

    6.7 测试用例 

    int main()
    {
    	List_address head;
    	Init_List_Address(&head);
    	Insert(&head, 12);
    	Insert(&head, 67);
    	Insert(&head, 56);
    	Insert(&head, 16);
    	Insert(&head, 25);
    	Insert(&head, 37);
    	Insert(&head, 22);
    	Insert(&head, 29);
    	Insert(&head, 15);
    	Insert(&head, 47);
    	Insert(&head, 48);
    	Insert(&head, 34);
    	Show(&head);
    
    	printf("-----------------------------\n");
    
    	Delete(&head, 25);
    	Delete(&head, 12345);
    	Show(&head);
    	return 0;
    }

    运行结果如下:

    相关文章:

  1. 推荐一款Nginx图形化管理工具: NginxWebUI
  2. 工业科学级天文相机:跨界融合的高精密成像解决方案
  3. wsl2+ubuntu22.04安装blender教程(详细教程)
  4. Deep Learning based Prediction Model for Adaptive Video Streaming论文简报
  5. 从数据海洋中“淘金”——数据挖掘的魔法与实践
  6. 深度解析 AutoGLM:智能时代的多面手
  7. Android Input——查找并添加目标窗口(七)
  8. 解决MYSQL不能远程登陆问题
  9. ubuntu24.04 cmake 报错 libldap-2.5.so.0 解决办法
  10. docker 容器正常启动但是连接不上
  11. 2025年4月通信科技领域周报(3.31-4.06):6G技术加速落地与全连接生态构建
  12. Redis——实现消息队列
  13. 【langchain库名解析】
  14. Vue环境搭建:vue+idea
  15. 几款开源网盘的比较
  16. windows 安装 pygame( pycharm)
  17. 基于DNS的负载均衡和反向代理负载均衡
  18. 川翔云电脑:D5 渲染摆脱硬件限制,云端高效创作
  19. 2025年常见渗透测试面试题-sql(题目+回答)
  20. oracle常见问题处理集锦
  21. 用自己头像做的圣诞视频网站/下拉关键词排名
  22. 河南住房和城乡建设委员会网站/网络营销软件条件
  23. 滨州内做网站的公司/semantic scholar
  24. 台湾网友做的二次元炒股网站/关键词查询神器
  25. 用php做电子商务网站/今日国内新闻最新消息
  26. 国外室内设计网站大全网站/企业网站设计与实现论文