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

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. WhaleScheduler为银行业全信创环境打造统一调度管理平台解决方案

    项目背景 数字金融是数字经济的重要支撑和驱动力.近年来,我国针对数字金融的发展政策频频出台,<金融科技发展规划 (2022-2025年)>.<"十四五"数字经济发 ...

  2. 首次尝试SeaTunnel同步Doris至Hive?这些坑你不能不避

    笔者使用SeaTunnel 2.3.2版本将Doris数据同步到Hive(cdh-6.3.2)首次运行时有如下报错,并附上报错的解决方案: java.lang.NoClassDefFoundError ...

  3. 04-canvas多根线条

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...

  4. 【CDQ分治】[P5094 [USACO04OPEN] MooFest G 加强版

    P5094 [USACO04OPEN] MooFest G 加强版 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include <bits/stdc++.h> us ...

  5. JavaScript设计模式样例十九 —— 职责链模式

    职责链模式(Chain of Responsibility Pattern) 定义:为请求创建了一个接收者对象的链. 目的:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接 ...

  6. java_Web

    开始进入学习java web部分 一.Socket技术 字节流传输 使用bytes[] 封装字节进行传输数据 文件传输 浏览器访问 使用http协议进行访问 二.MySQL数据库 环境 Phpstyd ...

  7. 5分钟说透chatgpt

    5分钟说清楚 --到底它为啥能这么火? --到底牛逼在哪? --到底我能用来干嘛?   把"他"想象成一个博览群书的人 想象一下,现在有一个知识非常渊博的一个人,博览群书,掌握了绝 ...

  8. get方法传参后端接收数据异常 - 特殊字符需转义

    get方法传参的时候,如果有特殊字符,如 + 等,无法被识别,导致后端处理异常,所以,get方式,如果有特殊字符,需要转义后再请求接口 1.java 特殊字符转义 URLEncoder.encode( ...

  9. Java 集合工具包

    Java 集合工具包 Java集合是java提供的工具包,包含了常用的数据结构:集合.链表.队列.栈.数组.映射等. Java集合工具包位置是java.util.* Java集合主要可以划分为4个部分 ...

  10. yum命令提示error: rpmdb: BDB0113 Thread/process,解决方法

    最近在做RHCE的题目,yum命令装vdo时,使用yum install命令的时候,提示error: rpmdb: BDB0113 Thread/process,具体错误如下: [root@node2 ...