哈希表(C语言版)

文章目录

  • 哈希表
    • 原理
    • 实现(无自动扩容功能)
      • 代码
      • 运行结果
    • 分析
    • 应用

哈希表

如何统计一段文本中,小写字母出现的次数?

显然,我们可以用数组 int table[26] 来存储每个小写字母出现的次数,而且这样处理,效率奇高。假如我们想知道字母’k’出现的次数,直接访问元素 table['k' - 'a'] 即可,时间复杂度为O(1)。

在现实生活中,我们经常需要存储键值对(key-value)数据,比如上面的 ‘a’:10, ‘b’:6,再比如账号:个人信息,关键字:网页等等。如果键的取值范围很小(比如上面的例子),那么我们可以用数组存储,为每一个键绑定一个索引。

但是,如果键的取值范围很大,那么数组的方式就行不通了。哈希表就是被设计用来解决这样一个问题的~

原理

哈希表的核心设计分为两个部分:

  1. 哈希函数。哈希函数将 key 转换为数组中的一个索引。理想情况下不同的 key 都能转换成不同的索引值。当然这只是理想情况,所以我们还需要处理两个或者多个 key 都散列到相同索引值的情况 (哈希冲突)。

    优秀的哈希函数需要满足这些特性(拓展):
    a. 运算速度快。
    b. 尽量使键平均分布
    c. 逆向非常困难
    d. 对数据非常敏感
    e. 哈希冲突的概率非常小哈希函数:模拟等概率随机分布事件。
    
  2. 处理哈希冲突。

    • 开放地址法:线性探测法、平方探测法、再散列法
    • 拉链法

实现(无自动扩容功能)

这里,我们也采用常用的拉链法来解决哈希冲突,如下图所示:

在这里插入图片描述

代码

// 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,也大量使用了哈希表数据结构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/71321.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

昆虫-目标检测数据集(包括VOC格式、YOLO格式)

昆虫-目标检测数据集&#xff08;包括VOC格式、YOLO格式&#xff09; 数据集&#xff1a; 链接: https://pan.baidu.com/s/1tYb8s-AVJgSp7SGvqdWF9A?pwdt76a 提取码: t76a 数据集信息介绍&#xff1a; 共有 6009 张图像和一一对应的标注文件 标注文件格式提供了两种&#x…

C++(23):unreachable

C++23在头文件 "><utility>定义了std::unreachable(),用于指示编译器,该段代码不应该被允许,因此编译器可以对该位置进行优化,如果一旦允许了该位置的代码,行为未定义: #include <utility> #include <iostream>using namespace std;int func(…

备战蓝桥杯 Day1 回顾语言基础

开启蓝桥杯刷题之路 Day1 回顾语言基础 1.配置dev 工具->编译选项->勾选编译时加入以下命令->设定编译器配置(release和debug)都要-> -stdc11 ->代码生成/优化->代码生成/优化->语言标准(-std)->ISO C11 ->代码警告->显示最多警告信息(-Wall)…

python学opencv|读取图像(七十五)人脸识别:Fisherfaces算法和LBPH算法

【1】引言 前序学习进程中&#xff0c;已经掌握了使用Eigenfaces算法进行的人脸识别。相关文章链接为&#xff1a; python学opencv|读取图像&#xff08;七十四&#xff09;人脸识别&#xff1a;EigenFaces算法-CSDN博客 在此基础上&#xff0c;学习剩余两种人脸识别算法&am…

React 前端框架介绍

什么是 React&#xff1f; React 是一个由 Facebook 开发并维护的开源 JavaScript 库&#xff0c;用于构建用户界面。它主要用于创建交互式用户界Face&#xff08;UI&#xff09;&#xff0c;尤其是当数据变化时需要更新部分视图时非常有效。React 的核心思想是组件化和声明性…

包装类的空指针异常

问题&#xff1a; Integer是引用对象&#xff0c;当初始化不进行赋值的时候是null,是null就不可以自动拆箱 实践&#xff1a; public class Test {public static void main(String[] args) {Integer num null;//自动拆箱int i num;} } 会包异常信息&#xff1a; Excepti…

【Elasticsearch】检索选定字段(Retrieve selected fields)

在 Elasticsearch 中&#xff0c;检索选定字段&#xff08;Retrieve selected fields&#xff09;是一个重要的功能&#xff0c;用于优化查询性能和减少数据传输量。以下是结合所有回答的详细总结&#xff0c;涵盖每种字段检索方式的原理、应用场景、性能特点以及限制。 1._so…

【MySQL】 基本查询(下)

欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;【MySQL】 基本查询(下) 发布时间&#xff1a;2025.2.18 隶属专栏&#xff1a;MySQL 目录 Update语法案例 Delete删除数据语法案例 截断表语法案例 插入查询结果语法案例 聚合函数函数介绍案例 group by子句的使用语…

docker 基础命令使用(ubuntu)

docker 状态查询 docker ps docker ps -adocker --version docker info docker --help docker run --help docker ps --help ...docker 操作镜像命令 docker imagesdocker rmi 镜像id/镜像名docker 操作容器命令 docker ps docker ps -adocker run 命令 # 端口映射 -p 参数…

idea 2023.3.7常用插件

idea 2023.3.7常用插件 文档 idea 2019.3常用插件idea 2023.3.7常用插件 idea 2023.3.7常用插件 插件名称插件版本说明1AceJump3.5.9AceJump允许您快速将插入符号导航到编辑器中可见的任何位置。只需按“ctrl&#xff1b;”&#xff0c;键入一个字符&#xff0c;然后在Ace …

基于Flask的广西高校舆情分析系统的设计与实现

【Flask】基于Flask的广西高校舆情分析系统的设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统综合运用Python、Flask框架及多种数据处理与可视化工具开发&#xff0c;结合Boot…

用什么办法能实现ubuntu里面运行的自己开发的python程序能自动升级。

要实现Ubuntu中自己开发的Python程序自动升级&#xff0c;可以通过以下几种方式&#xff1a; 1. 使用 Git 仓库 定时任务 如果你的Python程序托管在Git仓库中&#xff0c;可以通过定时拉取最新代码来实现自动升级。 步骤&#xff1a; 确保Python程序在Git仓库中。在Ubuntu上…

破解微服务疑难杂症:2025年全解决方案

微服务架构已经成为现代软件开发的主流选择&#xff0c;其优势在于能够将复杂的系统拆分为独立的服务模块&#xff0c;方便开发和维护。然而&#xff0c;在微服务的实施过程中&#xff0c;开发者往往会面临许多挑战&#xff0c;如服务间通信、数据一致性、性能优化和故障处理等…

Linux(Centos 7.6)命令详解:head

1.命令作用 将每个文件的前10行打印到标准输出(Print the first 10 lines of each FILE to standard output) 2.命令语法 Usage: head [OPTION]... [FILE]... 3.参数详解 OPTION: -c, --bytes[-]K&#xff0c;打印每个文件的前K字节-n, --lines[-]&#xff0c;打印前K行而…

NAT(网络地址转换)技术详解:网络安全渗透测试中的关键应用与防御策略

目录 NAT的作用 NAT类型 NAT工作流程示例 NAT 转换技术的原理 源地址转换&#xff08;SNAT&#xff0c;Source NAT&#xff09;&#xff1a; 目标地址转换&#xff08;DNAT&#xff0c;Destination NAT&#xff09;&#xff1a; 端口地址转换&#xff08;PAT&#xff0c…

【怎么使用Redis实现一个延时队列?】

怎么使用Redis实现一个延时队列? 详细说明Java代码示例解释注意事项使用Redis实现延时队列通常通过有序集合(Sorted Set)来实现,利用Redis的ZSET类型及其相关命令可以很方便地实现这一功能。 有序集合中的每个元素都有一个分数(score),我们可以利用这个分数来存储消息需…

STM32 I2C通信协议说明

目录 背景 I2C协议 数据的有效性 I2C通信开始和停止条件 I2C数据传输 发送 响应 正常情况&#xff1a; 异常情况&#xff1a; 主机结束接收 写寄存器的标准流程 读寄存器的标准流程 仲裁机制 时钟同步 SDA线的仲裁 程序 背景 对单片机的三大通信中的I2C通信进…

Android 10.0 移除wifi功能及相关菜单

介绍 客户的机器没有wifi功能&#xff0c;所以需要删除wifi相关的菜单&#xff0c;主要有设置-网络和互联网-WLAN,长按桌面设置弹出的WALN快捷方式&#xff0c;长按桌面-微件-设置-WLAN。 修改 Android10 上直接将config_show_wifi_settings改为false,这样wifi菜单的入口就隐…

DeepSeek HuggingFace 70B Llama 版本 (DeepSeek-R1-Distill-Llama-70B)

简简单单 Online zuozuo :本心、输入输出、结果 文章目录 DeepSeek HuggingFace 70B Llama 版本 (DeepSeek-R1-Distill-Llama-70B)前言vllm 方式在本地部署 DeepSeek-R1-Distill 模型SGLang 方式在本地部署 DeepSeek-R1-Distill 模型DeepSeek-R1 相关的 Models,以及 Huggin…

服务器中部署大模型DeepSeek-R1 | 本地部署DeepSeek-R1大模型 | deepseek-r1部署详细教程

0. 部署前的准备 首先我们需要足够算力的机器&#xff0c;这里我在vultr中租了有一张A16显卡一共16GB显存的服务器作为演示。部署的模型参数为14b的。如果需要部署满血版本671b的&#xff0c;需要更大的算力支持&#xff0c;这里由于是个人资金有限&#xff0c;就演示14b的部署…