哈希表是什么?

一、哈希表是什么?

哈希表,也称为散列表,是一种根据关键码值(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),但在最坏情况下,开放地址法的时间复杂度可能更高,因为它可能需要进行多次探测来找到空闲位置或查找元素。而链地址法则在最坏情况下可能面临链表过长的问题,但在良好的哈希函数和适当的哈希表大小下,这种情况是不常见的。因此,选择哪种方法取决于具体的应用场景和需求。

五、哈希表的应用场景


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

数据库索引

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

缓存系统

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

唯一性检查

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【字符串相加】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 字符串相加 方法一&#xff1a; 方法二&#xff1a; 总结 前言 世上有两种耀眼的光芒&#xff0c;一种是正在升起的太阳&#xff0c;一种是正在努力学习编程的…

给你N个整数,要求删除最大和最小的数之后按原顺序输出。

给你N个整数&#xff0c;要求删除最大和最小的数之后按原顺序输出。 输入输出格式 输入描述: 第一行输入一个整数N&#xff0c;N<100。 第二个输入N个整数。 输出描述: 按题意输出。#include <iostream> using namespace std; int main() {int n;cin >> n;int…

Vue2->3

Vue2->3 认识Vue31. Vue2 选项式 API vs Vue3 组合式API2. Vue3的优势 使用create-vue搭建Vue3项目1. 认识create-vue2. 使用create-vue创建项目 熟悉项目和关键文件组合式API - setup选项1. setup选项的写法和执行时机2. setup中写代码的特点3. <script setup>语法糖…

Scratch 第十六课-弹珠台游戏

第十六课-弹珠台游戏 大家好&#xff0c;今天我们一起做一款弹珠台scratch游戏&#xff0c;我们也可以叫它弹球游戏&#xff01;这款游戏在刚出来的时候非常火爆。小朋友们要认真学习下&#xff01; 这节课的学习目标 物体碰撞如何处理转向问题。复习键盘对角色的控制方式。…

ubuntu环境下docker容器详细安装使用

文章目录 一、简介二、ubuntu安装docker1.删除旧版本2.安装方法一3. 安装方法二&#xff08;推荐使用&#xff09;4.运行Docker容器5. 配置docker加速器 三、Docker镜像操作1. 拉取镜像2. 查看本地镜像3. 删除镜像4. 镜像打标签5. Dockerfile生成镜像 四、Docker容器操作1. 获取…

AtCoder ABC343 A-D题解

Problem A: 签到题。 #include <bits/stdc.h> using namespace std; int main(){int A,B;cin>>A>>B;for(int i0;i<10;i){if(i!(AB))cout<<i<<endl;//记得return} } Problem B: 依旧签到。 include <bits/stdc.h> using namespace …

实用工具:实时监控服务器CPU负载状态并邮件通知并启用开机自启

作用&#xff1a;在服务器CPU高负载时发送邮件通知 目录 一、功能代码 二、配置开机自启动该监控脚本 1&#xff0c;配置自启脚本 2&#xff0c;启动 三、功能测试 一、功能代码 功能&#xff1a;在CPU负载超过预设置的90%阈值时就发送邮件通知&#xff01;邮件内容显示…

求阶乘。。

&#xff01;&#xff01;&#xff01;答案解释摘录自蓝桥云课题解 问题描述 满足N!的末尾恰好有个0的最小的N是多少? 如果这样的N不存在输出-1。 输入格式 一个整数 K 输出格式 一个整数代表答案 样例输入 2 样例输出 10 import os import sys# 请在此输入您的代码 def coun…

懒人必备|视频号片段提取实战教程!

你是否也为如何提取视频号的视频感到困扰&#xff1f;想要留住那些美好瞬间&#xff0c;但又不知道改如何操作&#xff1f;别瞎找了&#xff01;今天就让我来教你正确的步骤&#xff0c;让你轻松成为“提取达人”&#xff01; 首先&#xff0c;打开想要提取的视频&#xff0c;找…

某u盘 对比 sd卡+读卡器

部分 u盘 性能甚至不如 读卡器SD卡 电脑 支持USB3 gen2 的 USBA接口(u盘用) 和 typec接口(读卡器用) 极致性能需求可考虑: m.2固态硬盘盒m.2固态 设备 速度对比 迅雷... 拷贝文件信息 共用格式化信息

青少年软件编程(Python)等级考试试卷(一级)2020年3月

青少年软件编程&#xff08;Python&#xff09;等级考试试卷&#xff08;一级&#xff09;2020年3月 第 1 题 【单选题】 运行下方代码段&#xff0c;输出的是( )。 print("a"*3) A :a3 B :3a C :a a a D :aaa 正确答案:D 试题解析 第 2 题 【单选题】 下…

SpringBoot整合rabbitmq-重复消费问题

说明&#xff1a;重复消费的原因大致是生产者将信息A发送到队列中&#xff0c;消费者监听到消息A后开始处理业务&#xff0c;业务处理完成后&#xff0c;监听在告知rabbitmq消息A已经被消费完成途中中断&#xff0c;也就时说我已经处理完业务&#xff0c;而队列中还存在当前消息…

【C++】类的默认成员函数(上)

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 一、默认成员函数二、构造函数构造函数的概念及特性 三、析构函数析构函数的特性…

#QT(DEMO2-登录界面)

1.IDE&#xff1a;QTCreator 2.实验&#xff1a;DEMO登录 3.记录 Line Edit输入不换行 密码框输入如下设置: 运行效果 4.代码

使用Matplotlib绘制圆环图

圆环图是饼图的修改版&#xff0c;中间区域被切掉。圆环图更关注使用弧的面积来以最有效的方式表示信息&#xff0c;而不是饼图&#xff0c;饼图更关注比较切片之间的比例面积。圆环图在空间方面更有效&#xff0c;因为圆环图内部的空白空间可用于显示有关圆环图的一些附加信息…

【C++那些事儿】深入理解C++类与对象:从概念到实践(中)| 默认构造函数 | 拷贝构造函数 | 析构函数 | 运算符重载 | const成员函数

&#x1f4f7; 江池俊&#xff1a; 个人主页 &#x1f525;个人专栏&#xff1a; ✅数据结构冒险记 ✅C那些事儿 &#x1f305; 有航道的人&#xff0c;再渺小也不会迷途。 文章目录 1. 类的6个默认成员函数2. 构造函数2.1 概念2.2 特性 3. 析构函数3.1 概念3.2 特性 4. 拷贝…

flutter 文字一行显示,超出换行

因为app有多语言&#xff0c;中文和其他语言长度不一致&#xff0c;可能导致英文会很长。 中文样式 英文样式 代码 Row(mainAxisAlignment: MainAxisAlignment.end,crossAxisAlignment: CrossAxisAlignment.end,children: [Visibility(visible: controller.info.fee ! null,ch…

产品经理岗位的任职资格和职业规划

产品经理主要是商业银行以客户为导向的&#xff0c;具体负责组织银行某一金融产品线的创新设计、生产营销和管理服务的工作。这类人士主要负责应用实施工作&#xff0c;其中产品线由一系列的产品构成&#xff0c;公司的产品经理主要分为全过程产品创新设计专家、全过程产品生产…

驾驭栈和队列,首先逃不开这些(有效括号、栈和队列相互模拟、循环队列)

一.有效括号 . - 力扣&#xff08;LeetCode&#xff09; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须…

力扣74. 搜索二维矩阵(二分查找)

Problem: 74. 搜索二维矩阵 文章目录 题目描述思路复杂度Code 题目描述 思路 思路1&#xff1a;映射为一维数组二分查找 1.由于题目矩阵中的元素整体是升序的&#xff0c;我们可以将其放置在一个大小为 m n m \times n mn的一维数组array中进行二分查找 2.对应的映射关系是ar…