哈希表的简单介绍

哈希表的概念

哈希表(Hash Table)也叫散列表,是根据键码值(key value)而直接进行访问的数据结构。它通过把关键码值映射到哈希表中的一个位置来访问记录,以加快查找速度。这个映射函数就叫做散列函数,存放记录的数组的散列表。

哈希表查找的时间复杂度

哈希表存储的是键值对,其查找的时间复杂度与元素数量的多少无关,哈希表在查找元素的时候通过计算哈希码值来定位元素的位置从而直接访问元素的,因此哈希表查找的时间复杂度为O(1)。

当向哈希表
    插入元素时:根据待插入元素的关键码,计算出该元素的存储位置,并进行存放。
查找元素时:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,
在结构中,按此位置取元素比较,若关键码相等,则搜索成功。
常见的哈希函数
  • 直接定制法  

    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B  
    优点:简单、均匀  
    缺点:需要事先知道关键字的分布情况  
    适合查找比较小且连续的情况  
    面试题:找出一个字符串中第一个只出现一次的字符,要求:时间复杂度O(N),空间复杂度O(1)

  • 除留余数法

    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key % p(p<=m),将关键码转换成哈希地址  

  • 折叠法
  • 数学分析法
  • 随机数法
  • 平方取中法
哈希冲突

即不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

那么我们如何解决哈希冲突呢?
常用的解决哈希冲突的方法有两种:开散列法闭散列法

闭散列
闭散列:也叫开放地址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到表中“下一个” 空位中去。

常用的探测方法有 :线性探测、二测谈探测等。

负载因子:
散列表的负载因子定义为:a = 填入表中的元素个数 / 散列表长度对于开放定址法,
散列荷载因子是重要的影响因素,应该严格限制在0.7~0.8,超过0.8,cpu缓冲不命
中按指数曲线上升。

开散列

开散列法又叫链地址法(开链法)。
开散列法:首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

通常,每个桶对应的链表结点都很少,将n个关键码通过某一个散列函数,存放到散列表中的m个桶中,那么每一个桶中链表的平均长度为 n / m 。以搜索平均长度为 n / m 的链表代替了搜索长度为 n 的顺序表,搜索效率快的多。

接下来我们将实现一下开散列以及闭散列,具体代码请参考我的另一篇博客:
哈希表具体实现

在 C 语言中,哈希表的实现通常依赖于数组和哈希函数。哈希表通过哈希函数将键(key)映射到数组的某个索引上,从而实现快速的插入和查找操作。由于哈希冲突的存在(即不同的键可能映射到相同的索引),需要解决冲突的方法,例如开放寻址法或链地址法。 ### 哈希表的基本实现 以下是一个使用开放寻址法实现的简单哈希表示例: ```c #include <stdio.h> #include <stdbool.h> #define MAXSIZE 20 bool Insert(int hash[MAXSIZE], int e, int length) { if (length < MAXSIZE) { int j = e % MAXSIZE; while (hash[j] != 0) { j = (++j % MAXSIZE); } hash[j] = e; length++; return true; } else { return false; } } void Print(int hash[MAXSIZE]) { printf("元素:"); for (int i = 0; i < MAXSIZE; i++) { printf("%d ", hash[i]); } } int Find(int hash[MAXSIZE], int key) { int j = key % MAXSIZE; int temp = j; do { if (hash[temp] == key) { return temp; } temp = (++temp % MAXSIZE); } while (hash[temp] != key && temp != j); return -1; } int main() { int hash[MAXSIZE] = {0}; int length = 0; int e, key, get; while (length < MAXSIZE) { printf("请输入插入元素(结束请插入0):"); scanf("%d", &e); if (e == 0) { break; } else { Insert(hash, e, length); } } Print(hash); while (true) { printf("\n请输入要查找的元素(0结束):"); scanf("%d", &key); if (key == 0) { printf("查找结束"); break; } else { get = Find(hash, key); if (get == -1) { printf("不存在"); } else { printf("%d", get); } } } } ``` ### 哈希表的使用方法 1. **初始化哈希表**:定义一个固定大小的数组,初始值为 `0` 或 `NULL`。 2. **插入元素**:使用哈希函数将键值映射到数组的索引位置。如果该位置已被占用,则根据冲突解决策略(如开放寻址法)寻找下一个可用位置。 3. **查找元素**:通过哈希函数计算键值的索引位置,并检查该位置是否存储了目标键值。如果未找到,则根据冲突解决策略继续搜索。 4. **删除元素**:查找目标键值的索引位置,并将其标记为可用或删除状态。 ### 使用 C 语言库函数实现哈希表 除了手动实现哈希表外,还可以使用 C 语言的一些库函数,例如 `uthash`,它提供了一种方便的方式来管理哈希表。以下是一个使用 `uthash` 的示例: ```c #include <stdio.h> #include <stdlib.h> #include "uthash.h" typedef struct MyHash { int key; int value; UT_hash_handle hh; } MyHash; MyHash *myhs = NULL; void insert(int key1, int value1) { MyHash *tmp1 = NULL; HASH_FIND_INT(myhs, &key1, tmp1); if (tmp1 == NULL) { tmp1 = (MyHash *)malloc(sizeof(MyHash)); tmp1->key = key1; tmp1->value = value1; HASH_ADD_INT(myhs, key, tmp1); } else { tmp1->value = value1; } } MyHash *find(int key1) { MyHash *tmp1 = NULL; HASH_FIND_INT(myhs, &key1, tmp1); return tmp1; } int main() { insert(1, 10); insert(2, 20); insert(3, 30); MyHash *result = find(2); if (result != NULL) { printf("找到键 %d,值为 %d\n", result->key, result->value); } else { printf("未找到键\n"); } return 0; } ``` ### 哈希表的优点 - **查找速度快**:理想情况下,哈希表可以在常数时间内完成插入和查找操作。 - **适合大数据量**:对于需要频繁查找和插入的场景,哈希表表现出色。 ### 哈希表的缺点 - **扩展困难**:由于哈希表基于数组实现,因此在数据量超出初始容量时,扩展较为困难。 - **不利于遍历**:哈希表不适合进行顺序遍历操作。 ### 总结 哈希表是一种高效的数据结构,适用于需要快速查找和插入的场景。在 C 语言中,可以通过数组和哈希函数手动实现哈希表,也可以使用第三方库(如 `uthash`)来简化实现过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值