详解哈希映射

news2024/11/14 10:49:46

哈希映射

  • 一、哈希映射概念回顾
    • 哈希表的组成
  • 二、哈希映射的基本流程
  • 三、哈希表的碰撞处理
    • 1. 链式法(Chaining)
    • 2. 开放地址法(Open Addressing)
  • 四、C语言中的哈希映射实现
    • 1. 哈希表的数据结构设计
    • 2. 哈希函数
    • 3. 插入操作
    • 4. 查找操作
    • 5. 删除操作
    • 6. 内存管理
      • 完整代码
  • 五、哈希表的性能分析
  • 六、哈希表的优缺点
      • 总结

哈希映射(Hash Map)是一种数据结构,它通过哈希函数将键(key)映射到对应的值(value)。在C语言中,哈希映射通常不是内建的类型,但我们可以使用数组、链表或动态内存管理等方式来实现一个哈希映射。

一、哈希映射概念回顾

哈希映射(Hash Map)是一种基于哈希表的数据结构,用于实现 键值对的映射。哈希表通过 哈希函数 将一个“键”(key)映射到表中的某个位置,并存储对应的“值”(value)。

哈希表的组成

  1. 数组(Hash Table Array):哈希表内部通常是一个数组,数组的每个元素是一个链表(或其他结构),用于存储冲突的键值对。
  2. 哈希函数(Hash Function):哈希函数将键转换成一个整数索引,这个索引决定了该键值对在数组中的位置。
  3. 碰撞处理(Collision Handling):不同的键可能经过哈希函数后映射到相同的索引,这叫做“碰撞”。为了处理碰撞,哈希表使用一些方法(如链式法、开放地址法)来解决。

二、哈希映射的基本流程

  1. 计算哈希值:哈希函数首先根据键(key)生成一个整数值,这个值通常称为“哈希值”。
  2. 映射到索引:将哈希值映射到哈希表的索引位置。通常是将哈希值对哈希表的大小取模(hash_value % table_size),这样可以保证生成的索引在数组范围内。
  3. 插入数据:将键值对存储在哈希表的相应位置。如果这个位置已经有其他数据(碰撞),就需要处理碰撞。
  4. 查找数据:根据键计算哈希值并找到对应位置,查看该位置是否有目标数据。
  5. 删除数据:根据键计算哈希值找到对应位置,然后将该键值对删除。

三、哈希表的碰撞处理

哈希表的一个重要挑战是如何有效处理碰撞。当两个不同的键映射到同一个哈希值(即数组索引相同)时,就会发生碰撞。哈希表常用的碰撞处理方法有两种:链式法(Chaining)开放地址法(Open Addressing)

1. 链式法(Chaining)

链式法通过为每个数组元素维护一个链表(或其他容器)来解决碰撞。当多个键值对有相同的哈希值时,它们就被插入到同一个链表中。具体步骤如下:

  • 如果哈希表某个位置上已经存在键值对,那么新的键值对就会被添加到该位置的链表中。
  • 查找时,通过遍历链表查找键值对。
  • 删除时,遍历链表找到该元素并删除。

链式法的优势在于不需要重新分配哈希表的大小,查找操作的时间复杂度平均为O(1),但最坏情况下会退化成O(n),例如所有元素都映射到同一个位置。

2. 开放地址法(Open Addressing)

开放地址法是另一种碰撞处理策略,它与链式法不同,不是将冲突的元素放入链表,而是将冲突的元素“重新探测”到哈希表的其他位置。常见的开放地址法有:

  • 线性探测(Linear Probing):当发生碰撞时,查找下一个空槽。
  • 二次探测(Quadratic Probing):通过二次方公式来探测下一个空槽。
  • 双重哈希(Double Hashing):使用第二个哈希函数来计算步长,避免线性探测中可能产生的聚集问题。

开放地址法的挑战在于哈希表的空间管理,因为元素存储在表内,空间一旦满了,就需要扩展哈希表的大小。

四、C语言中的哈希映射实现

在C语言中实现哈希映射的核心工作就是定义合适的数据结构,编写哈希函数,处理碰撞,提供插入、查找、删除等操作。

我们来逐步解释一个典型的 链式哈希表 实现(如前面的代码示例)。

1. 哈希表的数据结构设计

首先我们需要定义哈希表的基本结构。在这个例子中,我们使用了一个数组 table 来存储每个槽位的链表头。

typedef struct Entry {
    char *key;    // 键
    int value;    // 值
    struct Entry *next;  // 链表指针
} Entry;

typedef struct HashMap {
    Entry *table[TABLE_SIZE];  // 哈希表数组
} HashMap;
  • Entry 结构体表示哈希表中的单个元素,包含键、值和一个指向下一个元素的指针(链表结构)。
  • HashMap 结构体表示整个哈希表,table 数组存储了所有的哈希表槽位,每个槽位存储一个链表(如果发生碰撞)。

2. 哈希函数

哈希函数的作用是将键(在这里是字符串)转换成一个整数,然后将其映射到哈希表的索引。我们使用了一个简单的加权和哈希函数,它对每个字符的ASCII码值进行加权计算:

unsigned int hash(char *key) {
    unsigned int hashValue = 0;
    while (*key) {
        hashValue = (hashValue * 31) + *key;  // 乘以31是一个常见的加权因子
        key++;
    }
    return hashValue % TABLE_SIZE;  // 模哈希表大小,保证索引在合法范围内
}

哈希函数的质量对哈希表性能影响很大。一个好的哈希函数应尽量避免哈希冲突(即尽量均匀地分布键值对),并且计算速度要快。

3. 插入操作

当我们插入一个键值对时,首先通过哈希函数计算键的哈希值,确定该键值对应的索引位置。如果该位置已经有数据(发生碰撞),就将新元素添加到链表的头部。

void insert(HashMap *hashMap, char *key, int value) {
    unsigned int index = hash(key);
    Entry *newEntry = (Entry*)malloc(sizeof(Entry));
    newEntry->key = strdup(key);  // 复制键字符串
    newEntry->value = value;
    newEntry->next = hashMap->table[index];  // 新元素指向当前链表头
    hashMap->table[index] = newEntry;  // 更新该位置为新的链表头
}

4. 查找操作

查找时,我们首先计算键的哈希值,然后在对应的索引位置查找。如果该位置存储的是链表,我们就遍历链表直到找到目标键。

int search(HashMap *hashMap, char *key) {
    unsigned int index = hash(key);
    Entry *entry = hashMap->table[index];
    while (entry != NULL) {
        if (strcmp(entry->key, key) == 0) {
            return entry->value;  // 找到则返回值
        }
        entry = entry->next;  // 继续查找链表中的下一个元素
    }
    return -1;  // 没找到,返回-1
}

5. 删除操作

删除操作也类似于查找,先找到该位置,然后删除链表中的元素:

void delete(HashMap *hashMap, char *key) {
    unsigned int index = hash(key);
    Entry *entry = hashMap->table[index];
    Entry *prev = NULL;
    while (entry != NULL) {
        if (strcmp(entry->key, key) == 0) {
            if (prev == NULL) {
                hashMap->table[index] = entry->next;  // 删除头节点
            } else {
                prev->next = entry->next;  // 删除中间节点
            }
            free(entry->key);  // 释放键
            free(entry);  // 释放节点
            return;
        }
        prev = entry;
        entry = entry->next;
    }
}

6. 内存管理

哈希表的数据插入、删除、查找等操作都涉及到动态内存分配和释放,因此需要特别注意内存管理,确保不会发生内存泄漏。

完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TABLE_SIZE 10  // 哈希表大小

// 哈希表条目结构体
typedef struct Entry {
    char *key;
    int value;
    struct Entry *next;  // 用于链表处理碰撞
} Entry;

// 哈希表结构体
typedef struct HashMap {
    Entry *table[TABLE_SIZE];
} HashMap;

// 哈希函数:根据字符串计算哈希值
unsigned int hash(char *key) {
    unsigned int hashValue = 0;
    while (*key) {
        hashValue = (hashValue * 31) + *key;
        key++;
    }
    return hashValue % TABLE_SIZE;
}

// 创建一个新的哈希表
HashMap* createHashMap() {
    HashMap *hashMap = (HashMap*)malloc(sizeof(HashMap));
    for (int i = 0; i < TABLE_SIZE; i++) {
        hashMap->table[i] = NULL;
    }
    return hashMap;
}

// 插入键值对
void insert(HashMap *hashMap, char *key, int value) {
    unsigned int index = hash(key);
    Entry *newEntry = (Entry*)malloc(sizeof(Entry));
    newEntry->key = strdup(key);  // 复制键
    newEntry->value = value;
    newEntry->next = hashMap->table[index];
    hashMap->table[index] = newEntry;
}

// 查找键对应的值
int search(HashMap *hashMap, char *key) {
    unsigned int index = hash(key);
    Entry *entry = hashMap->table[index];
    while (entry != NULL) {
        if (strcmp(entry->key, key) == 0) {
            return entry->value;
        }
        entry = entry->next;
    }
    return -1;  // 没找到返回-1
}

// 删除键值对
void delete(HashMap *hashMap, char *key) {
    unsigned int index = hash(key);
    Entry *entry = hashMap->table[index];
    Entry *prev = NULL;
    while (entry != NULL) {
        if (strcmp(entry->key, key) == 0) {
            if (prev == NULL) {
                hashMap->table[index] = entry->next;
            } else {
                prev->next = entry->next;
            }
            free(entry->key);
            free(entry);
            return;
        }
        prev = entry;
        entry = entry->next;
    }
}

// 释放哈希表内存
void freeHashMap(HashMap *hashMap) {
    for (int i = 0; i < TABLE_SIZE; i++) {
        Entry *entry = hashMap->table[i];
        while (entry != NULL) {
            Entry *temp = entry;
            entry = entry->next;
            free(temp->key);
            free(temp);
        }
    }
    free(hashMap);
}

int main() {
    HashMap *hashMap = createHashMap();
    
    insert(hashMap, "apple", 100);
    insert(hashMap, "banana", 200);
    insert(hashMap, "cherry", 300);
    
    printf("apple: %d\n", search(hashMap, "apple"));
    printf("banana: %d\n", search(hashMap, "banana"));
    
    delete(hashMap, "banana");
    printf("banana after delete: %d\n", search(hashMap, "banana"));
    
    freeHashMap(hashMap);
    
    return 0;
}

五、哈希表的性能分析

  • 查找、插入和删除的平均时间复杂度:在理想情况下,哈希函数将键均匀地映射到哈希表的槽位中,所有操作的时间复杂度为O(1)。即使发生碰撞,链表的长度通常不会很长,因此复杂度仍然接近O(1)。
  • 最坏情况:在最坏情况下,所有元素可能都映射到哈希表的同一个槽位,变成一个链表。此时查找、插入和删除的时间复杂度就会退化为O(n),其中n是哈希表中元素的个数。

六、哈希表的优缺点

优点

  • 查找、插入、删除操作的时间复杂度平均为O(1),但最坏情况下为O(n)(当碰撞严重时)。
  • 能够实现快速的键值对映射,适用于需要快速查找的场景。

缺点

  • 内存开销较大,需要预先设置哈希表的大小。
  • 需要设计合适的哈希函数,以避免过多的碰撞。
  • 如果表的大小不合适或碰撞处理不好,性能可能大大降低。

总结

哈希映射(Hash Map)是一个非常高效的数据结构,通过哈希函数将键映射到数组索引,并利用碰撞处理方法(如链式法)来存储键值对。在C语言中,哈希映射的实现需要考虑哈希函数的设计、碰撞的处理、内存管理等多个方面。

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

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

相关文章

06.VSCODE:备战大项目,CMake专项配置

娇小灵活的简捷配置不过是年轻人谈情说爱的玩具&#xff0c;帝国大厦的构建&#xff0c;终归要交给CMake去母仪天下。一个没有使用 CMake 的 C 项目&#xff0c;就像未来世界里的一台相声表演&#xff0c;有了德纲却无谦&#xff0c;观众笑着遗憾。—— 语出《双城记》作者&…

jmeter常用配置元件介绍总结之后置处理器

系列文章目录 安装jmeter jmeter常用配置元件介绍总结之后置处理器 8.后置处理器8.1.CSS/JQuery提取器8.2.JSON JMESPath Extractor8.3.JSON提取器8.4.正则表达式提取器8.5.边界提取器8.5.Debug PostProcessor8.6.XPath2 Extractor8.7.XPath提取器8.8.结果状态处理器 8.后置处理…

淘宝/天猫按图搜索商品:taobao.item_search_img API的奇幻之旅

在这个看脸的时代&#xff0c;我们不仅对人要看颜值&#xff0c;连买东西都要“看脸”了。没错&#xff0c;我说的就是淘宝/天猫的按图搜索商品功能——taobao.item_search_img API。这个功能就像是电商平台的“人脸识别”&#xff0c;只不过它认的是商品的颜值。下面&#xff…

豆包MarsCode算法题:数组元素之和最小化

数组元素之和最小化 问题描述思路分析分析思路解决方案 参考代码&#xff08;Python&#xff09;代码分析1. solution 函数2. 计算 1 2 3 ... n 的和3. 乘以 k 得到最终的数组元素之和4. 主程序&#xff08;if __name__ __main__:&#xff09;代码的时间复杂度分析&#x…

已有账号,重装系统激活office后发现没有ppt,word,excel等

有时候重装系统后&#xff0c;登录windows结果右键没有word,excel等 点击进入office 进入右边的账户 找到设备和订阅 直接下载office 安装后就会出现了

【数据结构与算法】第12课—数据结构之归并排序

文章目录 1. 归并排序2. 计数排序3. 排序算法复杂度及稳定性分析在这里插入图片描述 1. 归并排序 分治法&#xff08;Divide and Conquer&#xff09;是一种重要的算法设计策略&#xff0c;其核心思想是将一个复杂的大问题分解为若干个小规模的子问题&#xff0c;递归地解决这些…

shell 100例

1、每天写一个文件 (题目要求&#xff09; 请按照这样的日期格式(xxxx-xx-xx每日生成一个文件 例如生成的文件为2017-12-20.log&#xff0c;并且把磁盘的使用情况写到到这个文件中不用考虑cron&#xff0c;仅仅写脚本即可 [核心要点] date命令用法 df命令 知识补充&#xff1…

微信公众平台申请(测试平台)

登录平台 微信公众平台 注册信息 回调路径不知道怎么填可以先不填&#xff0c;等写完项目就知道调用那个路径了 这样就注册好了

云原生-docker安装与基础操作

一、云原生 Docker 介绍 Docker 在云原生中的优势 二、docker的安装 三、docker的基础命令 1. docker pull&#xff08;拉取镜像&#xff09; 2. docker images&#xff08;查看本地镜像&#xff09; 3. docker run&#xff08;创建并启动容器&#xff09; 4. docker ps…

Qt 编写插件plugin,支持接口定义信号

https://blog.csdn.net/u014213012/article/details/122434193?spm1001.2014.3001.5506 本教程基于该链接的内容进行升级&#xff0c;在编写插件的基础上&#xff0c;支持接口类定义信号。 环境&#xff1a;Qt5.12.12 MSVC2017 一、创建项目 新建一个子项目便于程序管理【…

社会信任数据 NGO、CGSS、献血量(2000-2021)

非政府组织&#xff08;NGO&#xff09;是指在地方、国家或国际级别上组织起来的非营利性的、志愿性的公民组织。在中国&#xff0c;NGO通常被称为民间组织&#xff0c;包括社会团体、民办非企业单位和基金会。 2000年-2021年社会信任数据&#xff08;NGO、CGSS、献血量&#…

Odoo:免费开源的流程制造行业ERP管理系统

概述 聚焦流程制造连续性生产的特性&#xff0c;提供集成PLMERPMESBI的一体化解决方案&#xff0c;涵盖计划、生产、质量、配方、供销、库存、成本、设备、资金管理等业务领域的整体性解决方案 行业的最新洞察&行业典型痛点 一、生产过程需要精细化控制 需要在各种制约…

D3的竞品有哪些,D3的优势,D3和echarts的对比

D3 的竞品 ECharts: 简介: ECharts 是由百度公司开发的一款开源的 JavaScript 图表库&#xff0c;提供了丰富的图表类型和高度定制化的配置选项。特点: 易于使用&#xff0c;文档详尽&#xff0c;社区活跃&#xff0c;支持多种图表类型&#xff08;如折线图、柱状图、饼图、散点…

使用nossl模式连接MySQL数据库详解

使用nossl模式连接MySQL数据库详解 摘要一、引言二、nossl模式概述2.1 SSL与nossl模式的区别2.2 选择nossl模式的场景三、在nossl模式下连接MySQL数据库3.1 准备工作3.2 C++代码示例3.3 代码详解3.3.1 初始化MySQL连接对象3.3.2 连接到MySQL数据库3.3.3 执行查询操作3.3.4 处理…

C/C++内存管理 | new的机制 | 重载自己的operator new

一、C/C内存分布 1. 内存分区 栈又叫堆栈–非静态局部变量/函数参数/返回值等等&#xff0c;栈是向下增长的。内存映射段是高效的I/O映射方式&#xff0c;用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存&#xff0c;做进程间通信 .堆用于程序运行时动态内…

小面馆叫号取餐流程 佳易王面馆米线店点餐叫号管理系统操作教程

一、概述 【软件资源文件下载在文章最后】 小面馆叫号取餐流程 佳易王面馆米线店点餐叫号管理系统操作教程 点餐软件以其实用的功能和简便的操作&#xff0c;为小型餐饮店提供了高效的点餐管理解决方案&#xff0c;提高了工作效率和服务质量 ‌点餐管理‌&#xff1a;支持电…

单体架构 IM 系统之 Server 节点状态化分析

基于 http 短轮询模式的单体架构的 IM 系统见下图&#xff0c;即客户端通过 http 周期性地轮询访问 server 实现消息的即时通讯&#xff0c;也就是我们前面提到的 “信箱模型”。“信箱模型” 虽然实现非常容易&#xff0c;但是消息的实时性不高。 我们在上一篇文章&#xff08…

大语言模型理论基础

文章目录 前言大语言模型必需知识概述大语言模型目标模型上下文神经网络的神经元常见激活函数SigmoidTanhRelusoftmax 通用近似定理多层感知机&#xff08;MLP&#xff09;拟合最后 前言 你好&#xff0c;我是醉墨居士&#xff0c;我们接下来对大语言模型一探究竟&#xff0c;…

37.安卓逆向-壳-smali语法1

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;图灵Python学院 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要盲目相信。第一…

Arduino IDE Windows 系统 离线安装 esp32 开发板 亲测好用。

1、前提条件需要具备特殊网络。 2、官方文档地址&#xff1a;Installing - - — Arduino ESP32 latest documentation 3、系统&#xff1a;Windows10 Arduino IDE 版本2.3.3 之前安装的esp32开发板的版本是2.0.13&#xff0c;由于之前没有接触过esp32开发&#xff0c;也没…