在计算机世界中,哈希表如同一位聪慧的图书管理员。他知道如何计算索书号,从而可以快速找到目标图书。

1.哈希表的概念

哈希表(hash table),又称散列表,它通过建立键key 与值value 之间的映射,实现高效的元素查询。具体而言,我们向哈希表中输入一个键key ,则可以在(1) 时间内获取对应的值value 。

1.1哈希表的基本操作

  • 添加元素
  • 查询元素
  • 删除元素
    在哈希表中进行增删查改的时间复杂度都是(1),非常高效。

1.2哈希表的常用操作

哈希表的常见操作包括:初始化、查询操作、添加键值对和删除键值对等。

2.基于数实现哈希表

2.1哈希表的结构体定义

首先,我们将key 和value 封装成一个类Pair ,以表示键值对。

//键值对
typedef struct{
int key;
int *val;
}Pair;

其次,我们来定义哈希表的结构体

// 基于数组实现哈希表
typedef struct{
Pair* buckes[Max_Size];
}ArrayHashMap;

2.2哈希表的初始化

首先,为哈希表动态分配内存空间,其次,将哈希表中的每个槽(bucket)初始化为空或NULL,表示没有元素存储在这些槽中,如果初始化成功,返回哈希表指针.

// 哈希表的初始化
ArrayHashMap *InitHashMap(){
//为哈希表分配内存
ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap));
if(hmap == NULL){
printf("内存分配失败!\n");
return -1;
}
//将每一个键值对置空
for (int i = 0; i < Max_Size; i++){
hmap->buckets[i] = NULL;
}
return hmap;
}

2.3删除哈希表

首先,遍历一遍哈希表,将哈希表的键值对释放,再将哈希表的槽释放,最后将哈希表释放。

// 哈希表的删除
void DesttoryHashMap(ArrayHashMap *hmap){
for (int i = 0; i < Max_Size; i++)
{
if(hmap->buckets[i] != NULL){
free(hmap->buckets[i]->val);
free(hmap->buckets[i]);
}
}
free(hmap);
}

2.4哈希函数

哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有key ,输出空间是所有桶(数组索引)。换句话说,输入一个key ,我们可以通过哈希函数得到该key 对应的键值对在数组中的存储位置。

//哈希函数
unsigned int HashFunction(int key){
return abs(key) % Max_Size;
}

2.5查找哈希表中的元素

在哈希表中查找给定键的值。如果找到,返回对应的值;否则返回 -1。

// 哈希表的查找
int *SearchInHashMap(ArrayHashMap *hamp, int key){
unsigned int index = HashFunction(key);
if(hamp->buckets[index]->key == key){
return hamp->buckets[index]->val;
}
return -1; //没找到
}

2.6 删除哈希表中的元素

// 删除哈希表的元素
bool DeleteInHashMap(ArrayHashMap *hmap, int key){
unsigned int index = HashFunction(key);
if(hmap->buckets[index]->key == key){
hmap->buckets[index]->key = NULL;
return true;
}
return false;
}

2.7添加哈希表元素

将新的键值对添加到哈希表中。如果槽位已被占用,这里我不解决哈希冲突,则返回 false,表示添加失败

// 添加哈希表函数
bool AddHashMap(ArrayHashMap *hmap, int key, int val){
unsigned int index = HashFunction(key);
if(hmap->buckets[index]->key == key){
//这里我们不解决哈希冲突
printf("槽位已经被占用!\n");
return false;
}
hmap->buckets[index]->key = key;
hmap->buckets[index]->val = val;
return true;
}

3.哈希冲突与扩容

什么是哈希冲突?
从本质上看,哈希函数的作用是将所有key 构成的输入空间映射到数组所有索引构成的输出空间,而输入空间往往远大于输出空间。因此,理论上一定存在 “多个输入对应相同输出” 的情况。我们将这称之为哈希冲突。
容易想到,哈希表容量 越大,多个key 被分配到同一个桶中的概率就越低,冲突就越少。因此,我们可以通过扩容哈希表来减少哈希冲突。
类似于数组扩容,哈希表扩容需将所有键值对从原哈希表迁移至新哈希表,非常耗时;并且由于哈希表容量capacity 改变,我们需要通过哈希函数来重新计算所有键值对的存储位置,这进一步增加了扩容过程的计算开销。为此,编程语言通常会预留足够大的哈希表容量,防止频繁扩容。

4.链式地址改良哈希表

哈希冲突会导致查询结果错误,严重影响哈希表的可用性。为了解决该问题,每当遇到哈希冲突时,我们就进行哈希表扩容,直至冲突消失为止。此方法简单粗暴且有效,但效率太低,因为哈希表扩容需要进行大量的数据搬运与哈希值计算。为了提升效率,我们可以通过链式存储结构来改良哈希表。
基于链式存储结构哈希表发生了以下变化:

  • 查询元素:输入key ,经过哈希函数得到桶索引,即可访问链表头节点,然后遍历链表并对比key 以查找目标键值对。
  • 添加元素:首先通过哈希函数访问链表头节点,然后将节点(键值对)添加到链表中。
  • 删除元素:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标节点并将其删除。

4.1链式地址哈希表结构体定义

//键值对
typedef struct
{
int key;
int *val;
} Pair; //链表节点
typedef struct Node{
Pair* pair;
struct Node* next;
}Node; //哈希表节点定义
typedef struct {
int size; //键值对的数量
int capcity; //哈希表的容量
double loadThres; //触发扩容的负载因子阈值
int extendRatio; //扩容倍数
Node **buckets; //桶数组
}HashMapChaining;

4.2哈希表的初始化

//哈希表初始化
HashMapChaining *InitHashMap(){
HashMapChaining* hashmap = (HashMapChaining*)malloc(sizeof(HashMapChaining));
hashmap->size = 0;
hashmap->capcity = 4;
hashmap->loadThres = 2.0 / 3.0;
hashmap->extendRatio = 2;
hashmap->buckets = (Node**)malloc(sizeof(Node*)*hashmap->capcity);
for (int i = 0; i < hashmap->capcity; i++)
{
hashmap->buckets[i] = NULL;
}
return hashmap; }

4.3 哈希表的销毁

//哈希表的销毁
void DesrtroyHashMap(HashMapChaining *hashmap){
for (int i = 0; i < hashmap->capcity; i++){
Node* cur = hashmap->buckets[i];
while (cur){
Node* temp = cur;
cur = cur->next;
free(temp->pair);
free(temp);
}
}
free(hashmap->buckets);
free(hashmap);
}

4.4哈希函数

//哈希函数
int HashFuntion(HashMapChaining *hashmap, int key){
return key % hashmap->capcity;
}

4.5 负载因子

double loadFactor(HashMapChaining *hashMap){
return (double)hashMap->size / (double)hashMap->capcity;
}

4.6哈希表的扩容

/* 扩容哈希表*/
void extend(HashMapChaining *hashMap)
{
// 暂存原哈希表
int oldCapacity = hashMap->capcity;
Node **oldBuckets = hashMap->buckets;
// 初始化扩容后的新哈希表
hashMap->capcity *= hashMap->extendRatio;
hashMap->buckets = (Node **)malloc(hashMap->capcity * sizeof(Node *));
for (int i = 0; i < hashMap->capcity; i++)
{
hashMap->buckets[i] = NULL;
}
hashMap->size = 0;
// 将键值对从原哈希表搬运至新哈希表
for (int i = 0; i < oldCapacity; i++)
{
Node *cur = oldBuckets[i];
while (cur)
{
put(hashMap, cur->pair->key, cur->pair->val);
Node *temp = cur;
cur = cur->next;
// 释放内存
free(temp->pair);
free(temp);
}
}

哈希表(C语言实现)的更多相关文章

  1. 简单的哈希表实现 C语言

    简单的哈希表实现 简单的哈希表实现 原理 哈希表和节点数据结构的定义 初始化和释放哈希表 哈希散列算法 辅助函数strDup 哈希表的插入和修改 哈希表中查找 哈希表元素的移除 哈希表打印 测试一下 ...

  2. C语言-简单哈希表(hash table)

    腾讯三面的时候,叫我写了个哈希表,当时紧张没写好···结果跪了··· 回来后粪发涂墙,赶紧写了一个! 什么都不说了···先让我到厕所里面哭一会··· %>_<% 果然现场发挥,以及基础扎实 ...

  3. 哈希表(散列表)—Hash表解决地址冲突 C语言实现

    哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.具体的介绍网上有很详 ...

  4. 数据结构---哈希表的C语言实现(闭散列)

    原文地址:https://blog.csdn.net/weixin_40331034/article/details/79461705 构造一种存储结构,通过某种函数(hashFunc)使元素的存储位 ...

  5. C语言实现简单的哈希表

    这是一个简单的哈希表的实现,用c语言做的. 哈希表原理 这里不讲高深理论,只说直观感受.哈希表的目的就是为了根据数据的部分内容(关键字),直接计算出存放完整数据的内存地址. 试想一下,如果从链表中根据 ...

  6. 【编程学习】浅谈哈希表及用C语言构建哈希表!

    哈希表:通过key-value而直接进行访问的数据结构,不用经过关键值间的比较,从而省去了大量处理时间. 哈希函数:选择的最主要考虑因素--尽可能避免冲突的出现 构造哈希函数的原则是: ①函数本身便于 ...

  7. 浅谈MatrixOne如何用Go语言设计与实现高性能哈希表

    目录 MatrixOne数据库是什么? 哈希表数据结构基础 哈希表基本设计与对性能的影响 碰撞处理 链地址法 开放寻址法 Max load factor Growth factor 空闲桶探测方法 一 ...

  8. 哈希表的C语言实现

    首先介绍一下什么是哈希表.同线性表.树一样,哈希表也是一种数据结构,理想情况下可以不需要任何比较,一次存取便能得到所查记录.所以它的优点就是查找特定记录的速度快.因为哈希表是基于数组的,所以创建后就难 ...

  9. c语言构建哈希表

    /*哈希查找 *哈希函数的构造方法常用的有5种.分别是: *数字分析法 *平方取中法 *分段叠加 *伪随机数 *除留取余法 *这里面除留取余法比较常用 *避免哈希冲突常用的方法有4种: *开放定址法( ...

  10. 哈希表 -数据结构(C语言实现)

    读数据结构与算法分析 哈希表 一种用于以常数平均时间执行插入.删除和查找操作的数据结构. 但是是无序的 一般想法 通常为一个包含关键字的具有固定大小的数组 每个关键字通过散列函数映射到数组中 冲突:两 ...

随机推荐

  1. 【转载】 Makefile的静态模式%.o : %.c

    版权声明:本文为CSDN博主「猪哥-嵌入式」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/u012351051 ...

  2. 生成式 AI:机会与风险并存,企业该如何取舍?

    作者 | 李晨 编辑 | Debra Chen Gartner最近对全球2,500名高管进行的一项调查发现,近一半(45%)的人表示,ChatGPT的宣传促使他们增加人工智能(AI)投资.调查报告称, ...

  3. Mac M1 安装Homebrew

    /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"

  4. Python 潮流周刊#66:Python 的预处理器(摘要)

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  5. OpenTelemetry 实战:从零实现应用指标监控

    前言 在上一篇文章:OpenTelemetry 实战:从零实现分布式链路追踪讲解了链路相关的实战,本次我们继续跟进如何使用 OpenTelemetry 集成 metrics 监控. 建议对指标监控不太 ...

  6. 分库分表后全局唯一ID的四种生成策略对比

    分库分表之后,ID主键如何处理? 当业务量大的时候,数据库中数据量过大,就要进行分库分表了,那么分库分表之后,必然将面临一个问题,那就是ID怎么生成?因为要分成多个表之后,如果还是使用每个表的自增长I ...

  7. ICMAN触摸滑条滚轮方案

    ICMAN触摸滑条滚轮调光是一种利用触摸技术实现的调光控制方式,是一种更简单.直观且节能的调光方式,有效改善了用户的照明体验,并在智能家居和节能照明领域发挥着重要作用. 基于厦门晶尊微电子(ICMAN ...

  8. 有哪些让你「 爽到爆炸 」的 Windows 软件?

    前言 本文源于知乎的一个提问,如标题所示:有哪些让你「 爽到爆炸 」的 Windows 软件?今天大姚给大家分享6款C#/.NET开源且免费的Windows软件,希望可以帮助大家提高学习.开发.办公效 ...

  9. 原生JavaScript实现一个简单的Promise构造函数示例

    下面demo示例,只支持实例的then和catch,代码如下: function PromiseDiffer(fn){ var self = this; this.status = 'pendding ...

  10. SpringMVC —— REST风格简介

    REST风格简介 REST(Representational State Transfer),表现形式转换 传统风格资源描述形式 REST风格描述形式 优点 隐藏资源的访问行为,无法通过地址得知对资源 ...