哈希表(C语言版)
文章目录
- 哈希表
- 原理
- 实现(无自动扩容功能)
- 代码
- 运行结果
- 分析
- 应用
哈希表
如何统计一段文本中,小写字母出现的次数?
显然,我们可以用数组 int table[26] 来存储每个小写字母出现的次数,而且这样处理,效率奇高。假如我们想知道字母’k’出现的次数,直接访问元素 table['k' - 'a'] 即可,时间复杂度为O(1)。
在现实生活中,我们经常需要存储键值对(key-value)数据,比如上面的 ‘a’:10, ‘b’:6,再比如账号:个人信息,关键字:网页等等。如果键的取值范围很小(比如上面的例子),那么我们可以用数组存储,为每一个键绑定一个索引。
但是,如果键的取值范围很大,那么数组的方式就行不通了。哈希表就是被设计用来解决这样一个问题的~
原理
哈希表的核心设计分为两个部分:
-
哈希函数。哈希函数将 key 转换为数组中的一个索引。理想情况下不同的 key 都能转换成不同的索引值。当然这只是理想情况,所以我们还需要处理两个或者多个 key 都散列到相同索引值的情况 (哈希冲突)。
优秀的哈希函数需要满足这些特性(拓展): a. 运算速度快。 b. 尽量使键平均分布 c. 逆向非常困难 d. 对数据非常敏感 e. 哈希冲突的概率非常小哈希函数:模拟等概率随机分布事件。 -
处理哈希冲突。
- 开放地址法:线性探测法、平方探测法、再散列法
- 拉链法
实现(无自动扩容功能)
这里,我们也采用常用的拉链法来解决哈希冲突,如下图所示:

代码
// Hash.h#include <stdint.h>
#define N 10typedef char* K;
typedef char* V;typedef struct node {K key;V val;struct node* next;
} Node;typedef struct {Node* table[N];int size;int capacity;uint32_t hashseed; // 哈希种子 保证哈希桶位置映射的随机性
} HashMap;HashMap* hashmap_create();
void hashmap_destroy(HashMap* map);V hashmap_put(HashMap* map, K key, V val);
V hashmap_get(HashMap* map, K key);
void hashmap_delete(HashMap* map, K key);
// Hash.c#include "hash.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>HashMap* hashmap_create() {// calloc 方法HashMap* hashmap = (HashMap*)calloc(1, sizeof(HashMap));if (hashmap) {hashmap->size = 0;hashmap->capacity = N;hashmap->hashseed = time(NULL);}return hashmap;
}// hashfunc()
/* murmurhash2 */
uint32_t hash(const void* key, int len, uint32_t seed) {const uint32_t m = 0x5bd1e995;const int r = 24;uint32_t h = seed ^ len;const unsigned char* data = (const unsigned char*)key;while (len >= 4) {uint32_t k = *(uint32_t*)data;k *= m;k ^= k >> r;k *= m;h *= m;h ^= k;data += 4;len -= 4;}switch (len){case 3: h ^= data[2] << 16;case 2: h ^= data[1] << 8;case 1: h ^= data[0];h *= m;};h ^= h >> 13;h *= m;h ^= h >> 15;return h;
}V hashmap_put(HashMap* map, K key, V val) {// a. 如果key不存在,添加key-val,并返回NULL// b. 如果key存在,更新key关联的val,返回原来的valint idx = hash(key, strlen(key), map->hashseed) % map->capacity; // 确定哈希桶Node* cur = map->table[idx];while (cur) {if (strcmp(cur->key, key) == 0) { // 如果key存在V oldVal = cur->val;cur->val = val;printf("有重复key, 已将旧值:%s 更换为新值:%s\n", oldVal, val);return oldVal;}cur = cur->next;} // cur == NULL// key不存在的情况,插入新的键值对Node* newNode = (Node*)malloc(sizeof(Node));newNode->key = key;newNode->val = val;newNode->next = map->table[idx]; // 头插法map->table[idx] = newNode; // 更新哈希桶的地址map->size++;printf("插入键值对 key: %s val: %s\n", key, val);return NULL;
}V hashmap_get(HashMap* map, K key) {// a. 如果key不存在,返回NULL// b. 如果key存在,返回key关联的valint idx = hash(key, strlen(key), map->hashseed) % map->capacity; // 确定哈希桶Node* cur = map->table[idx];while (cur) {if (strcmp(cur->key, key) == 0) { // key 存在printf("找到了目标键:%s 对应的值为:%s\n", cur->key, cur->val);return cur->val;}cur = cur->next;}// key不存在printf("没找到目标键 %s 对应的键值对\n", key);return NULL;
}void hashmap_delete(HashMap* map, K key) {int idx = hash(key, strlen(key), map->hashseed) % map->capacity; // 确定哈希桶Node* cur = map->table[idx];Node* prev = NULL;while (cur) {if (strcmp(cur->key, key) == 0) { // 找到了目标键if (prev == NULL) // 第一个结点map->table[idx] = cur->next;else prev->next = cur->next;printf("键值对 key: %s val: %s 已释放\n", cur->key, cur->val);free(cur);map->size--;return;}prev = cur;cur = cur->next;}// 没有找到目标键printf("没找到目标键 %s 对应的键值对,无法删除\n", key);
}void hashmap_destroy(HashMap* map) {// 1. 释放所有结点printf("即将释放哈希表中共 %d 对键值对\n", map->size);for (int i = 0; i < map->capacity; i++) {Node* cur = map->table[i];while (cur) {Node* freeNode = cur;cur = cur->next;printf("键值对 key: %s val: %s 已释放\n", freeNode->key, freeNode->val);free(freeNode);} // cur == NULL}// 2. 释放map->tablefree(map->table);// 3. 释放map结构体free(map);printf("哈希表释放成功\n");
}
// main.c
#include "hash.h"
#include <stdlib.h>
#include <stdio.h>int main(void) {HashMap* map = hashmap_create();hashmap_put(map, "1", "tom");hashmap_put(map, "2", "jack");hashmap_get(map, "1");hashmap_put(map, "1", "jane");hashmap_get(map, "1");hashmap_get(map, "100");hashmap_delete(map, "1");hashmap_get(map, "1");hashmap_put(map, "3", "musk");hashmap_put(map, "4", "musk");hashmap_put(map, "5", "musk");hashmap_put(map, "6", "musk");hashmap_destroy(map);return 0;
}
运行结果

分析
在哈希函数保证 key 平均分布的前提下,那么哈希表的性能就取决于链表的平均长度 (L)。
put : O(L)
先对 key 进行哈希,找到对应的链表,然后遍历链表,判断是添加结点还是更新结点。
get : O(L)
先对 key 进行哈希,找到对应的链表,然后遍历链表,找到对应的结点。
delete : O(L)
先对 key 进行哈希,找到对应的链表,然后遍历链表,删除对应的结点。
如果我们想在常数时间复杂度内, 完成哈希表的增删查操作,那么我们就得控制链表的平均长度不超过某个值。这个值我们称之为加载因子(load factor),也就是链表平均长度可以达到的最大值。
因此,当元素个数达到一定的数目的时候,我们就需要对数组进行扩容(哈希种子也需要重新生成,防止极端情况:所有结点都在一个哈希桶中),然后把所有元素重新映射到哈希表中。
应用
哈希表的应用很广,比如 C++ 中的 unordered_map , unordered_set 和 Java 中的 HashMap, HashSet 底层的数据结构都是哈希表。再比如,常用的缓存中间件 Redis,也大量使用了哈希表数据结构。
相关文章:
哈希表(C语言版)
文章目录 哈希表原理实现(无自动扩容功能)代码运行结果 分析应用 哈希表 如何统计一段文本中,小写字母出现的次数? 显然,我们可以用数组 int table[26] 来存储每个小写字母出现的次数,而且这样处理,效率奇高。假如我们想知道字…...
内容中台驱动企业数字化内容管理高效协同架构
内容概要 在数字化转型加速的背景下,企业对内容管理的需求从单一存储向全链路协同演进。内容中台作为核心支撑架构,通过统一的内容资源池与智能化管理工具,重塑了内容生产、存储、分发及迭代的流程。其核心价值在于打破部门壁垒,…...
LLaMA-Factory DeepSeek-R1 模型 微调基础教程
LLaMA-Factory 模型 微调基础教程 LLaMA-FactoryLLaMA-Factory 下载 AnacondaAnaconda 环境创建软硬件依赖 详情LLaMA-Factory 依赖安装CUDA 安装量化 BitsAndBytes 安装可视化微调启动 数据集准备所需工具下载使用教程所需数据合并数据集预处理 DeepSeek-R1 可视化微调数据集处…...
vue 文件下载(导出)excel的方法
目前有一个到处功能的需求,这是我用过DeepSeek生成的导出(下载)excel的一个方法。 1.excel的文件名是后端生成的,放在了响应头那里。 2.这里也可以自己制定文件名。 3.axios用的是原生的axios,不要用处理过的ÿ…...
【第4章:循环神经网络(RNN)与长短时记忆网络(LSTM)— 4.3 RNN与LSTM在自然语言处理中的应用案例】
咱今天来聊聊在人工智能领域里,特别重要的两个神经网络:循环神经网络(RNN)和长短时记忆网络(LSTM),主要讲讲它们在自然语言处理里的应用。你想想,平常咱们用手机和别人聊天、看新闻、听语音助手说话,背后说不定就有 RNN 和 LSTM 在帮忙呢! 二、RNN 是什么? (一)…...
LLMs Ollama
LLMs 即大型语言模型(Large Language Models),是人工智能领域基于深度学习的重要技术,以下是关于它的详细介绍: 定义与原理 定义:LLMs 是一类基于深度学习的人工智能模型,通过海量数据和大量计…...
Blackbox.AI:高效智能的生产力工具新选择
前言 在当今数字化时代,一款高效、智能且功能全面的工具对于开发者、设计师以及全栈工程师来说至关重要。Blackbox.AI凭借其独特的产品特点,在众多生产力工具中脱颖而出,成为了我近期测评的焦点。以下是我对Blackbox.AI的详细测评࿰…...
计算机专业知识【 轻松理解数据库四大运算:笛卡尔积、选择、投影与连接】
在数据库的世界里,有几个关键的运算操作,就像是神奇的魔法工具,能帮助我们对数据进行各种处理和组合。今天,咱们就来聊聊笛卡尔积运算、选择运算、投影运算和连接运算这四大运算,用超简单的例子让小白也能轻松理解。 …...
C/C++字符串格式化全解析:从printf到std::format的安全演进与实战指南
目录 C 语言中的格式化函数对比 1. printf / fprintf / sprintf 的异同 C 中的字符串格式化 1. 流式输出 (std::ostringstream) 2. C20/23 格式化库 (std::format,需编译器支持) 跨语言对比与最佳实践 实战建议 总结 C 语言中的格式化函数对比 1. printf / …...
【C++】stack 和 queue 的适配器模式与实现
> 🍃 本系列为初阶C的内容,如果感兴趣,欢迎订阅🚩 > 🎊个人主页:[小编的个人主页])小编的个人主页 > 🎀 🎉欢迎大家点赞👍收藏⭐文章 > ✌️ 🤞 …...
【python】You-Get
文章目录 1、介绍2、安装与使用文档3、下载图片4、下载视频5、下载音乐6、参考 1、介绍 You-Get is a tiny command-line utility to download media contents (videos, audios, images) from the Web, in case there is no other handy way to do it. 源码:https…...
PHP基础部分
但凡是和输入、写入相关的一定要预防别人植入恶意代码! HTML部分 语句格式 <br> <hr> 分割符 <p>插入一行 按住shift 输入! 然后按回车可快速输入html代码(VsCode需要先安装live server插件) html:<h1>标题 数字越大越往后</h1> <p…...
gitee SSH 公钥设置教程
Gitee 提供了基于 SSH 协议的 Git 服务,在使用 SSH 协议访问仓库仓库之前,需要先配置好账户 SSH 公钥。 1、生成秘钥 Windows 用户建议使用 Windows PowerShell 或者 Git Bash,在 命令提示符 下无 cat 和 ls 命令。 ssh-keygen -t ed25519 -C "Gitee SSH Key"中间…...
Java零基础入门笔记:(3)程序控制
前言 本笔记是学习狂神的java教程,建议配合视频,学习体验更佳。 【狂神说Java】Java零基础学习视频通俗易懂_哔哩哔哩_bilibili Scanner对象 之前我们学的基本语法中我们并没有实现程序和人的交互,但是Java给我们提供了这样一个工具类&…...
鸡兔同笼问题
鸡兔同笼问题是这样一个问题: 现有鸡、兔合装在一个笼子里。数头一共100个头,数脚一共300只脚。问有多少只鸡多少只兔? 在这里讨论这个问题的解法当然太小儿科了。但是y_tab这个C语言解释器只提供了1维数组。如果需要用到2维数组时ÿ…...
【Pytorch 库】自定义数据集相关的类
torch.utils.data.Dataset 类torch.utils.data.DataLoader 类自定义数据集示例1. 自定义 Dataset 类2. 在其他 .py 文件中引用和使用该自定义 Dataset torch_geometric.data.Dataset 类torch_geometric.data.Dataset VS torch.utils.data.Dataset 详细信息,参阅 tor…...
electron打包基本教程
从0开始搭建 概要步骤基础软件运行项目打包项目 注意事项 概要 将html打包成桌面的主流有electron和nwjs,nwjs更加简单,但是使用效果不如electron,electron打包比较麻烦,但是效果比较好,反正各有优势和缺点 步骤 基…...
实现pytorch注意力机制-one demo
主要组成部分: 1. 定义注意力层: 定义一个Attention_Layer类,接受两个参数:hidden_dim(隐藏层维度)和is_bi_rnn(是否是双向RNN)。 2. 定义前向传播: 定义了注意力层的…...
深入Flask:如何优雅地处理HTTP请求与响应
哈喽,大家好,我是木头左! 本文将带你深入了解如何在Flask中优雅地处理HTTP请求和响应,让你的应用更加高效、安全和用户友好。 创建一个简单的Flask应用 让从创建一个最简单的Flask应用开始: from flask import Flaskapp = Flask(__name__)@app.route(/) def...
JVM ②-双亲委派模型 || 垃圾回收GC
这里是Themberfue 在上节课对内存区域划分以及类加载的过程有了简单的了解后,我们再了解其他两个较为重要的机制,这些都是面试中常考的知识点,有必要的话建议背出来,当然不是死记硬背,而是要有理解的背~~~如果对 JVM …...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
