哈希表(C语言版)

news2025/4/21 13:26:41

文章目录

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

哈希表

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

显然,我们可以用数组 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 10

typedef 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,返回原来的val
    int 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关联的val
    int 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->table
    free(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.coloradmin.cn/o/2300774.html

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

相关文章

亚马逊企业购大客户业务拓展经理张越:跨境电商已然成为全球零售电商领域中熠熠生辉的强劲增长点

2024年12月26日-27日&#xff0c;由中国产业海外发展协会上合-海湾双链专委会指导、极新主办的「重度垂直2024极新AIGC峰会」先后在深圳、香港两地顺利开幕。本届峰会以AI的垂直应用与出海为核心主题&#xff0c;旨在深入探讨AI技术在全球范围内的融合应用与发展趋势&#xff0…

VirtualBox 中使用 桥接网卡 并设置 MAC 地址

在 VirtualBox 中使用 桥接网卡 并设置 MAC 地址&#xff0c;可以按照以下步骤操作&#xff1a; 步骤 1&#xff1a;设置桥接网卡 打开 VirtualBox&#xff0c;选择你的虚拟机&#xff0c;点击 “设置” (Settings)。进入 “网络” (Network) 选项卡。在 “适配器 1” (Adapt…

idea无法联网,离线安装插件

插件地址&#xff1a;https://plugins.jetbrains.com/ JetBrains Marketplace 如果无法进入&#xff0c;可以试试 配置hosts 3.163.125.103 plugins.jetbrains.com ip 变了&#xff0c;可以查询个最新的&#xff1a; https://tool.chinaz.com/speedtest/plugins.jetbrai…

网络安全中的机器学习

当涉及到网络安全时&#xff0c;技术一直是保护系统免受攻击和数据泄露的关键。在这篇论文中&#xff0c;我将介绍一些当前在网络安全领域使用的关键技术&#xff0c;包括加密&#xff0c;身份验证和防火墙。 首先&#xff0c;加密是网络安全中最常见的技术之一。加密是指使用算…

halcon 条形码、二维码识别、opencv识别

一、条形码 函数介绍 create_bar_code_model * 1.创建条码读取器的模板 * 参数一&#xff1a;通用参数的名称&#xff0c;针对条形码模型进行调整。默认值为空 * 参数二&#xff1a;针对条形码模型进行调整 * 参数三&#xff1a;条形码模型的句柄。 create_bar_code_model (…

平板作为电脑拓展屏

有线串流&#xff08;速度更快&#xff09; spacedesk 打开usb对安卓的连接 用usb线直接连接电脑和平板 无线串流&#xff08;延迟高&#xff0c;不推荐&#xff09; todesk pc和手机端同时下载软件&#xff0c;连接后可以进行远程控制或扩展屏幕 spacedesk 连接到同一个…

【算法与数据结构】字典树(Trie)详解

目录 一&#xff0c;字典树的定义 二&#xff0c;字典树的代码实现 完整代码详细注释&#xff1a; 测试用例测试结果&#xff1a; 三&#xff0c;处理其他字符 四&#xff0c;内存优化与扩展 1. 内存优化 2. 扩展功能 五&#xff0c;扩展功能支持通配符匹配 六&…

【JavaEE进阶】MyBatis通过注解实现增删改查

目录 &#x1f343;前言 &#x1f340;打印日志 &#x1f334;传递参数 &#x1f38b;增(Insert) &#x1f6a9;返回主键 &#x1f384;删(Delete) &#x1f332;改(Update) &#x1f333;查(Select) &#x1f6a9;起别名 &#x1f6a9;结果映射 &#x1f6a9;开启驼…

Simulink Ststeflow教程 — 2 创建和编辑状态

目录 2.1 创建和编辑状态 2.1.1 状态的创建 2.1.2 创建连接节点 2.1.3 转移 2.1.5 默认转移 在Stateflow模型中&#xff0c;将包含有状态的Stateflow框图称状态图而将不包含任何状态的Stateflow框图称为流程图。其中&#xff0c;状态图是Stateflow最常用的一种形式&#x…

Fiddler笔记

文章目录 一、与F12对比二、核心作用三、原理四、配置1.Rules:2.配置证书抓取https包3.设置过滤器4、抓取App包 五、模拟弱网测试六、调试1.线上调试2.断点调试 七、理论1.四要素2.如何定位前后端bug 注 一、与F12对比 相同点&#xff1a; 都可以对http和https请求进行抓包分析…

线上就医全流程医药机构接入文档接口代码-医保就医接口php-demo版本

2025年2月18日11:28:03 国密算法开发库推荐 lpilp/guomi 我测试过php 7.2 - 8.0都可以兼容&#xff0c;如果有能力可以自己开发 目前已经开发了核心的接口的测试demo,并且封装了工具类直接写业务逻辑即可&#xff0c;并且已经有线上项目在使用&#xff0c;如果需要demo代码可…

智能选路+NAT实验 作业

拓扑图 配置ip 防火墙安全区域划分 用户配置 dns透明

LLM:RAG

原文链接&#xff1a;LLM&#xff1a;RAG 1、RAG 概览 RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09;是一种结合了信息检索&#xff08;IR&#xff09;和 LLM 的技术。它的核心思想是在 LLM 生成回答之前&#xff0c;通过检索相关文档…

基于Qt 和微信小程序的用户管理系统:WebSocket + SQLite 实现注册与登录

目录 一. 概要 二. 技术栈 三. 系统功能设计 3.1 功能模块 3.2 数据表设计 四. 具体实现 4.1 Qt 服务端 4.1.1 初始化 WebSocket 服务器 4.1.2 用户管理界面 4.2 微信小程序端 4.2.1 注册功能 4.2.2 登录功能 五. 运行效果 六. 源码下载 一. 概要 在物联网和智能设备…

策略+适配器模式详解

文章目录 1.策略模式1.目录结构2.Strategy.java 策略接口3.StrategyA.java 策略A4.StrategyB.java 策略B5.StrategyC.java 策略C6.Context.java 策略上下文7.Client.java 客户端8.小结 2.适配器模式1.目录结构2.CustomPaymentProcessor.java 自己的支付接口3.PayPalPaymentServ…

未来游戏:当人工智能重构虚拟世界的底层逻辑

未来游戏&#xff1a;当人工智能重构虚拟世界的底层逻辑 在《赛博朋克2077》夜之城的霓虹灯下&#xff0c;玩家或许已经注意到酒吧里NPC开始出现微表情变化&#xff1b;在《艾尔登法环》的开放世界中&#xff0c;敌人的战术包抄逐渐显露出类人智慧。这些细节预示着游戏产业正站…

Zabbix——Rocky9安装zabbix相关步骤记录

安装Zabbix 安装MariaDB 这里用MariaDB演示 https://mariadb.org/download/?trepo-config&dRedHatEnterpriseLinux9&v10.11&r_mneusoft 通过这个网址获得连接 选择对应的repo 根据系统版本和要安装的版本选择对应的repo 安装 新建一个repo文件&#xff0c;例…

startai产品精修教程

1.把产品放置ps画布中 打开startai插件选择产品精修功能&#xff0c;选择金属材质即可哦 调节一下参数就可以啦&#xff0c;最终效果图 下载地址&#xff1a;StartAI画图软件官网_PS插件StartAI绘画软件生成器_Photoshop图像处理插件

【YOLOv8】

文章目录 1、yolov8 介绍2、创新点3、模型结构设计3.1、backbone3.2、head 4、正负样本匹配策略5、Loss6、Data Augmentation7、训练、推理8、分割 Demo附录——V1~V8附录——相关应用参考 1、yolov8 介绍 YOLOv8 是 ultralytics 公司在 2023 年 1 月 10 号开源的 YOLOv5 的下…

【保姆级教程】DeepSeek R1+RAG,基于开源三件套10分钟构建本地AI知识库

一、总体方案 目前在使用 DeepSeek 在线环境时&#xff0c;页面经常显示“服务器繁忙&#xff0c;请稍后再试”&#xff0c;以 DeepSeek R1 现在的火爆程度&#xff0c;这个状况可能还会持续一段时间&#xff0c;所以这里给大家提供了 DeepSeek R1 RAG 的本地部署方案。最后实现…