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

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. python语言版(代码):计算百分数的概率单位

    相关资料: [转载]百分数的概率单位变换--解惑:概率确实没有单位但是数学里面确实有"概率单位"这个词 百分比与概率单位对照表 https://www.docin.com/p-22 ...

  2. DataOps真能“降本增效”?

    在各行各业中,越来越多的公司开始重视收集数据,并寻找创新方法来获得真实可行的商业成果,并且愿意投入大量时间和金钱来实现这一目标. 据IDC称,数据和分析软件及云服务市场规模在 2021 年达到了 90 ...

  3. Ambiguous Mapping 的处理方法

    出现原因:两个不同的Controller 中出现相同映射路径 eg: AController:/v1/wdnmd/userList/list BController:/v1/wdnmd/userLis ...

  4. Mysql 不用Json存储 List<Long>的方式

    public static void main(String[] args) { List<Long> idList = new ArrayList<>(); for (int ...

  5. Java核心技术之Date相关

    import java.time.DayOfWeek; import java.time.LocalDate; import java.util.Scanner; /** * @author Suns ...

  6. SMU Summer 2023 Contest Round 7

    SMU Summer 2023 Contest Round 7 A. Two Rival Students 答案不能大于 \(n-1\): 如果竞争对手之间的当前距离小于 \(n - 1\) ,我们总 ...

  7. bat2exe

    https://blog.csdn.net/qq_23452385/article/details/109145151

  8. React 18 自定义 Hook 获取 useState 最新值

    原理:通过同步更新 useRef  来获取最新值 // util.ts export const useRefState = (init: any = null) => { const [sta ...

  9. github拉取项目执行npm i 失败的问题

    一般卡在core-js没反应,然后报错的第一行是和node-sass有关的,基本上都是node-sass版本问题,这时候只需要在命令行输入两行代码就行 先把原来的依赖删掉 npm uni node-s ...

  10. spark 新建一个column并用另一column的最大值赋值

    finalDF.withColumn("NEW_COLUMN", max("start_date").over()).show()   Ref: https:/ ...