哈希表是什么?

news2024/11/16 1:42:20

一、哈希表是什么?

哈希表,也称为散列表,是一种根据关键码值(Key value)直接进行访问的数据结构。它通过把关键码值映射到表中一个位置来访问记录,从而加快查找速度。这个映射函数叫做散列函数(哈希函数),而存放记录的数组则称为散列表。

二、哈希表的构成(开放地址法)

我们先了解一下哈希表是如何构成的?
实现一个基本的哈希表数据结构涉及到几个核心步骤:
1、创建哈希表本身的数据结构,
2、定义一个哈希函数,
3、处理哈希冲突(本文以开放地址法为例)

开放地址法(Open Addressing)是哈希表解决哈希冲突的一种策略。当发生哈希冲突时,即两个或更多的键具有相同的哈希值,开放地址法通过在数组中查找下一个可用的空位来解决冲突。这种方法的核心思想是不再依赖哈希函数给出的数组下标,而是顺序查看表中的下一个单元,直到找到一个空单元或查遍全表。

开放地址法通常有以下三种具体实现方式:


线性探测:这是最简单的一种开放地址法。当发生冲突时,顺序查看表中下一个单元,直到找到一个空单元或查遍全表。这种方法的核心思想是“一旦发生冲突,就去寻找下一个空的散列表地址”。

二次探测:这是另一种开放地址法,它在发生冲突时,不是简单地顺序查看下一个单元,而是根据某种二次方程来确定下一个要查看的单元位置。

双重散列:这种方法使用两个哈希函数。当第一个哈希函数发生冲突时,使用第二个哈希函数来确定下一个要查看的单元位置。

这里我们用C语言更加方便举例,后面附带代码图解。

首先是创建哈希表本身的数据结构


#define TABLE_SIZE 10  

//先定义一个有key和value两种属性的结构体
//该结构体的名字是KeyValuePair
typedef struct {  
    int key;  
    int value;  
} KeyValuePair;  

  
// 哈希表结构  
//定义一个结构体,里面有KeyValuePair结构体的数组
//该结构体的名字是HashTable
typedef struct {  
    KeyValuePair pairs[TABLE_SIZE];  
    //暂时定为哈希表的容量为10
    //数组名为pairs
    
} HashTable;  

定义一个哈希函数

// 简单的哈希函数  
unsigned int hash(int key) { 
    //将key值求余以表的容量10 
    return key % TABLE_SIZE;  
}  

定义一个初始化哈希表的函数

#define EMPTY -1

// 初始化哈希表  
void initHashTable(HashTable *table) {  
    for (int i = 0; i < TABLE_SIZE; i++) {  
    //table是指针,所以要用->去解引用再访问
    //table->pairs[i].key 首先解引用 table 指针以获取 HashTable 结构体,
    //然后访问其 pairs 数组的第 i 个元素,
    //最后访问该元素的 key 成员
    //将key值赋值成EMPTY(即-1)
        table->pairs[i].key = EMPTY;  
    }  
}  

创建一个插入值到哈希表的函数,里面需要进行哈希冲突的处理

哈希冲突:将数据插入表中时,数据通过哈希函数计算出来的索引值来找到自己的位置,而数组索引处已有数据,产生冲突。
通俗点,两个数据想要插在数组的同一位置,必须要做一定处理

// 向哈希表中插入键值对  
void insert(HashTable *table, int key, int value) {  
    unsigned int index = hash(key);  
    //通过哈希函数对Key值的运算得到索引index的值



	//产生哈希冲突进行开放寻址,即重新为新来的数据找到空的位置
    while (table->pairs[index].key != EMPTY) { 
    
    	
        // 如果发现key值完全相同,说明用户想覆盖值,则替换值  
        if (table->pairs[index].key == key) {  
            table->pairs[index].value = value;  
            return;  
        }  
        // 线性探测下一个位置  
        index = (index + 1) % TABLE_SIZE;  
    } 
     
    // 找到空位置并插入  
    table->pairs[index].key = key;  
    table->pairs[index].value = value;  
}  

创建一个查询哈希表数据的函数

// 从哈希表中检索值  
int get(HashTable *table, int key) {  
    unsigned int index = hash(key);  
    while (table->pairs[index].key != EMPTY) {  
        // 找到键,返回对应的值  
        if (table->pairs[index].key == key) {  
            return table->pairs[index].value;  
        }  
        // 线性探测下一个位置  
        index = (index + 1) % TABLE_SIZE;  
    }  
    // 没找到键,返回错误值  
    return -1;  
}  

测试主函数

int main() {  
    HashTable table;  
    initHashTable(&table);  
  
    // 插入键值对  
    insert(&table, 10, 100);  
    insert(&table, 20, 200);  
    insert(&table, 30, 300);  
  
    // 检索值  
    printf("Value for key 20: %d\n", get(&table, 20));  
  
    // 更新值  
    insert(&table, 20, 2000);  
    printf("Updated value for key 20: %d\n", get(&table, 20));  
  
    return 0;  
}

例子代码图解

图【1】
在这里插入图片描述
图【2】

在这里插入图片描述
图【3】
在这里插入图片描述
图【4】
在这里插入图片描述
图【5】
在这里插入图片描述
图【6】
在这里插入图片描述
图【7】

在这里插入图片描述
图【8】
在这里插入图片描述

三、哈希表的构成(链地址法)

处理哈希冲突时,主要有开放地址法和链地址法这两种方法,上面的代码和图解已经介绍了开放地址法

而链地址法也是一种优雅的冲突解决策略。


在这里插入图片描述
实现步骤:


初始化:
创建一个数组,每个元素都是一个链表的头指针。
哈希函数:选择一个合适的哈希函数,将键映射到数组的某个索引。

插入:
计算键的哈希值,找到对应的数组索引。
如果该索引的链表为空,直接插入数据。
如果链表不为空,遍历链表查找:
若找到相同键,更新值。
若未找到,在链表末尾插入新数据。

查找:
计算键的哈希值,找到对应的链表。
遍历链表,比较键,直到找到或遍历完。

删除:
计算键的哈希值,找到对应的链表。
遍历链表,找到要删除的数据并移除。

链地址法通过链表巧妙地解决了哈希冲突,使得哈希表在冲突较多的情况下仍能保持高效。

四、开放地址法与链地址方法时间复杂度比较

开放地址法

开放地址法的时间复杂度分析相对复杂,因为它依赖于哈希表的装载因子(即已存储元素数量与哈希表大小的比值)以及哈希函数的均匀分布性。

插入操作

平均情况:当哈希表的大小足够大且装载因子较低时,插入操作的平均时间复杂度接近O(1),因为空闲位置较多,冲突较少。
最坏情况:当哈希表的大小较小或装载因子接近1时,插入操作的时间复杂度可能退化为O(n),其中n是哈希表中的元素数量。这是因为连续的冲突可能导致需要遍历整个哈希表来找到空闲位置。

查找操作

平均情况:与插入操作类似,当哈希表的大小足够大且装载因子较低时,查找操作的平均时间复杂度接近O(1)。
最坏情况:当哈希表的大小较小或装载因子接近1时,查找操作的时间复杂度可能退化为O(n),特别是当发生堆积(即大量元素集中在哈希表的某些区域)时。

链地址法
链地址法的时间复杂度分析相对简单,因为它不依赖于哈希表的装载因子。

插入操作

平均情况:无论哈希表的大小和装载因子如何,插入操作的平均时间复杂度都是O(1),因为插入总是在链表的末尾进行。
最坏情况:最坏情况下,当所有的元素都哈希到同一个桶(即链表)时,插入操作的时间复杂度为O(n),其中n是链表的长度。然而,这种情况在良好的哈希函数和适当的哈希表大小下是很少见的。

查找操作
平均情况:查找操作的平均时间复杂度是O(1 + α),其中α是链表的平均长度。这是因为查找操作通常只需要遍历链表一次。
最坏情况:最坏情况下,当所有的元素都哈希到同一个桶时,查找操作的时间复杂度为O(n),其中n是链表的长度。

开放地址法和链地址法在平均情况下的时间复杂度都是O(1),但在最坏情况下,开放地址法的时间复杂度可能更高,因为它可能需要进行多次探测来找到空闲位置或查找元素。而链地址法则在最坏情况下可能面临链表过长的问题,但在良好的哈希函数和适当的哈希表大小下,这种情况是不常见的。因此,选择哪种方法取决于具体的应用场景和需求。

五、哈希表的应用场景


哈希表因其高效的查找和插入性能,被广泛应用于各种场景。以下是哈希表的一些典型应用:

数据库索引

数据库索引是哈希表的一个重要应用。通过索引,可以快速定位到数据库中的特定数据,提高查询效率。

缓存系统

缓存系统通常使用哈希表来存储热点数据,以便快速响应请求。当数据被访问时,将其存储在哈希表中,以便后续快速查找。

唯一性检查

哈希表可以用于检查数据的唯一性。通过计算数据的哈希值,可以快速判断数据是否已经存在于哈希表中。

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

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

相关文章

C#与VisionPro联合开发——单例模式

单例模式 单例模式是一种设计模式&#xff0c;用于确保类只有一个实例&#xff0c;并提供一个全局访问点来访问该实例。单例模式通常用于需要全局访问一个共享资源或状态的情况&#xff0c;以避免多个实例引入不必要的复杂性或资源浪费。 Form1 的代码展示 using System; usi…

初阶数据结构之---栈和队列(C语言)

引言 在顺序表和链表那篇博客中提到过&#xff0c;栈和队列也属于线性表 线性表&#xff1a; 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构。线性表在逻辑上是线性结构&#xff0c;也就是说是连…

c++之拷贝构造和赋值

如果一个构造函数中的第一个参数是类本身的引用&#xff0c;或者是其他的参数都有默认值&#xff0c;则该构造函数为拷贝构造函数。 那么什么是拷贝构造呢&#xff1f;利用同类对象构造一个新对象。 1&#xff0c;函数名和类必须同名。 2&#xff0c;没有返回值。 3&#x…

差分题练习(区间更新)

一、差分的特点和原理 对于一个数组a[]&#xff0c;差分数组diff[]的定义是: 对差分数组做前缀和可以还原为原数组: 利用差分数组可以实现快速的区间修改&#xff0c;下面是将区间[l, r]都加上x的方法: diff[l] x; diff[r 1] - x;在修改完成后&#xff0c;需要做前缀和恢复…

4.关联式容器

关联式container STL中一些常见的容器&#xff1a; 序列式容器&#xff08;Sequence Containers&#xff09;&#xff1a; vector&#xff08;动态数组&#xff09;&#xff1a; 动态数组&#xff0c;支持随机访问和在尾部快速插入/删除。list&#xff08;链表&#xff09;&am…

奇舞周刊第521期:“一切非 Rust 项目均为非法”

奇舞推荐 ■ ■ ■ 拜登&#xff1a;“一切非 Rust 项目均为非法” 科技巨头要为Coding安全负责。这并不是拜登政府对内存安全语言的首次提倡。“程序员编写代码并非没有后果&#xff0c;他们的⼯作⽅式于国家利益而言至关重要。”白宫国家网络总监办公室&#xff08;ONCD&…

Python3零基础教程之数学运算专题进阶

大家好,我是千与编程,今天已经进入我们Python3的零基础教程的第十节之数学运算专题进阶。上一次的数学运算中我们介绍了简单的基础四则运算,加减乘除运算。当涉及到数学运算的 Python 3 刷题使用时,进阶课程包含了许多重要的概念和技巧。下面是一个简单的教程,涵盖了一些常…

NOC2023软件创意编程(学而思赛道)python初中组决赛真题

目录 下载原文档打印做题: 软件创意编程 一、参赛范围 1.参赛组别:小学低年级组(1-3 年级)、小学高年级组(4-6 年级)、初中组。 2.参赛人数:1 人。 3.指导教师:1 人(可空缺)。 4.每人限参加 1 个赛项。 组别确定:以地方教育行政主管部门(教委、教育厅、教育局) 认…

嵌入式驱动学习第一周——linux的休眠与唤醒

前言 本文介绍进程的休眠与唤醒。 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程&#xff0c;未来预计四个月将高强度更新本专栏&#xff0c;喜欢的可以关注本博主并订阅本专栏&#xff0c;一起讨论一起学习。现在关注就是老粉啦&#xff01; 行文目录 前言1. 阻塞和非阻…

Doris实战——美联物业数仓

目录 一、背景 1.1 企业背景 1.2 面临的问题 二、早期架构 三、新数仓架构 3.1 技术选型 3.2 运行架构 3.2.1 数据模型 纵向分域 横向分层 数据同步策略 3.2.2 数据同步策略 增量策略 全量策略 四、应用实践 4.1 业务模型 4.2 具体应用 五、实践经验 5.1 数据…

【Java EE】线程安全的集合类

目录 &#x1f334;多线程环境使用 ArrayList&#x1f38d;多线程环境使⽤队列&#x1f340;多线程环境使⽤哈希表&#x1f338; Hashtable&#x1f338;ConcurrentHashMap ⭕相关面试题&#x1f525;其他常⻅问题 原来的集合类, 大部分都不是线程安全的. Vector, Stack, HashT…

EndNote 21:文献整理与引用,一键轻松搞定 mac/win版

EndNote 21是一款功能强大的文献管理软件&#xff0c;专为学术研究者、学生和教师设计。它提供了全面的文献管理解决方案&#xff0c;帮助用户轻松整理、引用和分享学术文献。 EndNote 21软件获取 EndNote 21拥有直观的用户界面和强大的文献检索功能&#xff0c;用户可以轻松地…

昇腾ACL应用开发之硬件编解码dvpp

1.前言 在我们进行实际的应用开发时&#xff0c;都会随着对一款产品或者AI芯片的了解加深&#xff0c;大家都会想到有什么可以加速预处理啊或者后处理的手段&#xff1f;常见的不同厂家对于应用开发的时候&#xff0c;都会提供一个硬件解码和硬件编码的能力&#xff0c;这也是抛…

【C++干货基地】揭秘C++11常用特性:内联函数 | 范围for | auto自动识别 | nullptr指针空值

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

基于springboot实现校园爱心捐赠互助管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现校园爱心捐赠互助管理系统演示 摘要 随着互联网及电子商务平台的飞速发展&#xff0c;利用在线平台实现的二手商品交易以及在线捐赠已经非常普遍&#xff0c;很多高校目前还存在贫困生需要通过爱心人士的捐助来完成学业&#xff0c;同时很多高校的大学生也希…

【C++】STL学习之旅——初识STL,认识string类

string类 1 STL 简介2 STL怎么学习3 STL缺陷4 string4.1 初识 string4.2 初步使用构造函数成员函数 5 小试牛刀Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下一篇文章见&#xff01;&#xff01;&#xff01; 1 STL 简介 …

PyCharm如何添加python库

1.使用pip命令在国内源下载需要的库 下面使用清华源&#xff0c;在cmd中输入如下命令就可以了 pip install i https://pypi.tuna.tsinghua.edu.cn/simple 包名版本号2.如果出现报错信息&#xff0c;Cannot unpack file…这种情况&#xff0c;比如下面这种 ERROR: Cannot unpa…

考研复试指南

1. 记住&#xff0c;复试的本质不是考试&#xff0c;而是一场自我展示。 考研复试并非简单的知识考察&#xff0c;更是一场展示自我能力和潜力的机会。除了学科知识&#xff0c;考官更关注你的综合素质、学术兴趣和未来发展规划。因此&#xff0c;要保持自信&#xff0c;用更全…

前端canvas项目实战——简历制作网站(五):右侧属性栏(字体、字号、行间距)

目录 前言一、效果展示二、实现步骤1. 优化代码&#xff0c;提取常量2. 实现3个编辑模块3. 实现updateFontProperty方法4. 一个常见的用法&#xff1a;仅更新当前选中文字的样式 三、Show u the code后记 前言 上一篇博文中&#xff0c;我们扩充了线条对象&#xff08;fabric.…

带你快速初步了解Python字典

1.字典 定义多个数据一般使用列表&#xff0c;但是列表也存在一定的缺陷 若列表中有多个元素&#xff0c;想访问其中某个元素&#xff0c;比较不方便 定义字典的语法&#xff1a;{key1:value1, key2:value2, key3:value3......} 字典和列表习惯的使用场景&#xff1a; &qu…