数据结构:哈希表 | C++中的set与map

news2025/4/17 12:12:26

上回说到,红黑树是提升了动态数据集中频繁插入或删除操作的性能。而哈希表(Hash Table),则是解决了传统数组或链表查找数据必须要遍历的缺点。


哈希表

哈希表的特点就是能够让数据通过哈希函数存到表中,哈希函数能够将数据处理为表中位置的索引(这个就叫映射),然后将其存入。这样一来,我们查找数据的时候只要一通过哈希函数就可以得到该数据的索引,然后进行操作。

哈希函数有很多种实现方法。

1、直接定址法

数据本身就直接作为哈希地址(即位置索引)然后进行存放,好处就是不会产生哈希冲突。

可以看到我们的存入的数据是分散在表中的,只有3个数却占用了6个位置。这也是为什么哈希表又叫散列表。这种方法只适合数据集中的情况。

2、除留余数法

这是最经典的方法。通过指定一个除数,将我们的数据进行相除来得到哈希地址。

这种方法可以有效地减少空间浪费,将数据集中起来。但是如果像2、22这样的数就会产生哈希冲突。其解决方法之后会讲到。

3、平方取中法

这个通过将数据平方计算后,根据哈希表的大小取中间的几位数,这样更有机会让数据随机均匀分布在哈希表中。

如1234的平方为1522756,哈希表的大小是100,那么去中间3位,存入227的地址中。

现在谈谈如何解决哈希冲突,一般有两种方法。

1、开放寻址法(闲散列)

顾名思义就是让空地址向发生冲突的新数据开放,让其存进去。比如线性探测和二次探测。线性探测是指按照顺序将新数据排在原本哈希地址的后面,先+1,不行+2,依次类推。而二次探测就是把这些数进行了平方再加,向前后两方进行。

那我们如果一直发生冲突,导致哈希表大小不够用了怎么办?这里需要引入“负载因子”的概念。负载因子是一个比例,表示当哈希表中的数据比哈希表的大小超过负载因子时,哈希表需要扩容,一般为0.7。

2、链地址法(开散列)

哈希桶法,拉链法一般都是指这个。

就是把每个位置作为一个链表的头,如果发生冲突,就将其作为后继节点连接在该位置下面。

虽然这样的负载因子可以大于一(即不用扩容),但因为要存储指针,会造成更多的空间开销。

我们讨论哈希表时常常会谈到“键值对”。这里的“键 Key”就是钥匙的意思,可以通过键来获取哈希地址。所以我们上文所操作的所有数据,都可以叫做键,关键码值常常也说的是这个。而另一个“值 Value”指的是与键关联的数据,其存在真正赋予了哈希表以灵活性与实用性,因为键一旦在哈希表中确认就不便更改了,而值可以随便的更新删除。所以键更像成为了值的索引,从而适应常用的操作。

之前上文中介绍的都是值为空、只有键的情况,这可以称为是哈希表实现的“集合 Set”,而包含键值对的情况称为哈希表实现的“映射 Map”。

上文提到的哈希桶,就是指储存了键值对的一个位置。“哈希桶法”就是因为一个桶下面连着挂着好几个桶而得名的。

C++中的set与map

现在终于到了追本溯源的时候了。C++的STL库中,分别有着用红黑树实现的<set><map>,还有着用哈希表实现的<unordered_set><unordered_map>,这两种结构下最显著的区别就是其有序性了,红黑树默认是升序排列的。

上次讲到红黑树的查找删除操作时间复杂度都是O(logn),而哈希表是O(1)(平均复杂度,最坏情况O(n),持续哈希冲突),可以说是这种结构最大的优势了,但其需要预分派桶数组,拉链法还有存新链表,造成更多的空间占用。那么<unordered_set>和<unordered_map>大多会用在不关心顺序,追求快速操作的场景,其他情况都用<set><map>即可。

除了STL容器的通用方法以外,集合和映射还有这些用法。

//集合
set<int> s1 = { 1, 3, 2 };
unordered_set<int> s2 = { 1, 3, 2 };
s1.count(1);//在set中由于不重复性,可用于检测存在否
s2.count(1);//返回0或1
//对于有序的set
cout << *s1.lower_bound(1) << endl;//找到大于等于目标值的数迭代器
cout << *s1.upper_bound(1) << endl;//找到大于目标值的数迭代器
//对于无序的unordered_set
int num = s2.bucket_count();//桶的数量
cout << num << endl;
cout << s2.load_factor() << endl;//查看负载因子
s2.rehash(11);//设置桶的数量,重哈希,这个过程也包含对照负载因子扩容
num = s2.bucket_count();
cout << num << endl;
//映射
map<int, int> m1 = { {1, 3}, {2, 9}, {3, 4} };//前为key后为value
unordered_map<int, int> m2 = { {1, 3}, {2, 9}, {3, 4} };
m1.count(1);//检测存在否
m2.count(1);
//插入键值对,若键已存在则不覆盖
m1.emplace(2, 1);
m2.emplace(2, 1);
m1.insert({ 4, 6 });//这个返回迭代器
m2.insert({ 4, 6 });
//通过[key]访问value
cout << m1[1] << endl;
cout << m2[4] << endl;
//对于有序的map     //用first和second访问key和value
cout << m1.lower_bound(1)->first << endl;//找到key大于等于目标值的数迭代器
cout << m1.upper_bound(1)->second << endl;//找到key大于目标值的数迭代器
//对于无序的unordered_map
cout << m2.bucket(3) << endl;//返回key3的桶编号
m2.reserve(10);//预分配空间

 正好,一道名为“数组的度”的题就是用哈希表解决的,就在这里练练手吧!

    int findShortestSubArray(vector<int>& nums) {
        //key用来记录对应的数,vector用来记录索引
        unordered_map<int, vector<int>> m; 
        //第一次记录
        for(int i = 0; i < nums.size(); i++){
            if(!m.count(nums[i])){ 
                //分别是 出现次数、头索引、尾索引
                m[nums[i]] = {1, i, i};
            }else{  
                //增加次数,更新尾索引
                m[nums[i]][0]++;
                m[nums[i]][2] = i;
            }
        }
        //寻找出现次数最大的
        int num = 0, minLength = 0;
        //注意,获取的每一个是键值对类型pair<T1, T2>
        for(pair<int, vector<int>> it : m){
            if(num < it.second[0]){ 
                num = it.second[0]; 
                minLength = it.second[2] - it.second[1] + 1; 
            }else if(num == it.second[0]){ 
                if(minLength > it.second[2] - it.second[1] + 1){ 
                    minLength = it.second[2] - it.second[1] + 1;
                }
            }
        }
        return minLength;
    }   


小结

最近笔者还真抽不出时间好好学习游戏开发有关的知识,七七八八的事情挺多。不过瓜熟蒂落,水到渠成,把事情都梳理顺畅,再着手沉淀也不晚。

如有补充纠正欢迎留言。

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

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

相关文章

容器初始化Spring Boot项目原理,即web项目(war)包涉及相关类对比详解

以下是关于 SpringBootServletInitializer、ServletContainerInitializer、SpringServletContainerInitializer、WebApplicationInitializer 和 ServletInitializer 的对比详解及总结表格&#xff1a; 1. 核心对比详解 (1) SpringBootServletInitializer 作用&#xff1a; S…

[ctfshow web入门] web29

前置知识 eval: 把字符串按照 PHP 代码来执行&#xff0c;例如eval(“echo 1;”);这个函数拥有回显 system&#xff1a;使php程序执行系统命令&#xff0c;例如&#xff0c;system(“ls”);就是查看当前目录&#xff0c;这个拥有回显 preg_match&#xff1a;查找字符串是否匹配…

5.JVM-G1垃圾回收器

一、什么是G1 二、G1的三种垃圾回收方式 region默认2048 三、YGC的过程(Step1) 3.1相关代码 public class YGC1 {/*-Xmx128M -XX:UseG1GC -XX:PrintGCTimeStamps -XX:PrintGCDetails -XX:UnlockExperimentalVMOptions -XX:G1LogLevelfinest128m5% 60%6.4M 75M*/private stati…

Odrive0.5.1-FOC电机控制 arm_cos_f32.cpp arm_sin_f32.cpp代码实现(一)

01 查表法 在 our_arm_cos_f32 函数中&#xff0c;查表&#xff08;Look-Up Table, LUT&#xff09; 的核心是 预计算的正弦值表 sinTable_f32&#xff0c;通过巧妙利用余弦与正弦的相位关系实现快速余弦计算。以下是详细解析&#xff1a; 1. 查的是什么表&#xff1f; (1) 表内…

机械臂只有位置信息是否可以进行手眼标定?

平常我在做手眼标定时&#xff0c;一般都是通过OpenCV的cv::calibrateHandEye函数进行求解&#xff0c;需要输入多组不同的机械臂位姿。今天遇到了一款舵机机器人&#xff0c;只能获取位置&#xff0c;得不到姿态信息&#xff0c;想着那就把姿态都设为0&#xff0c;结果求不出来…

Python 数据分析01 环境搭建教程

Python 数据分析01 环境搭建教程 一、安装 Python 环境 访问 Python 官方网站 Python 官网&#xff0c;选择适合你操作系统的 Python 版本进行下载。下载完成后&#xff0c;运行安装程序。在安装过程中&#xff0c;建议选择“Add Python to PATH”选项&#xff0c;这样可以在…

使用 Visual Studio 2022 (VS2022) 编译 FreeCAD 1.0.0 的详细教程

一、环境准备 官方教程&#xff1a;在 Windows 上编译 - FreeCAD Documentation Windows 10/11&#xff08;推荐&#xff09; git vs2022 cmake 3.26.4 Doxygen1.12 二、获取源码与依赖 版本关系 打开Git Bash或CMD&#xff0c;执行以下命令 git clone --recurse-sub…

蓝桥杯单片机频率

long int Freq; unsigned int Timer_1000Ms; 加上 TMOD | 0x05; void Timer0Init(void) //0毫秒12.000MHz {AUXR & 0x7F; //定时器时钟12T模式TMOD & 0xF0; //设置定时器模式TMOD | 0x05;TL0 0x00; //设置定时初值TH0 0x00; //设置定时初值TF0 0; //清除TF0标…

遵循IEC 62304:构建安全可靠的医疗器械软件

目录 一、IEC 62304 标准概述 1. 标准定位与适用范围 二、核心内容与要求 1. 软件安全等级&#xff08;Software Safety Classification&#xff09; &#xff08;1&#xff09;分级标准 &#xff08;2&#xff09;分级依据 &#xff08;3&#xff09;验证要求 2. 软件…

互联网三高-数据库高并发之分库分表

1 数据库概述 1.1 数据库本身的瓶颈 ① 连接数 MySQL默认最大连接数为100&#xff0c;允许的最大连接数为16384 ② 单表海量数据查询性能 单表最好500w左右&#xff0c;最大警戒线800w ③ 单数据库并发压力问题 MySQL QPS&#xff1a;1500左右/秒 ④ 系统磁盘IO、CPU瓶颈 1.2 数…

UE5 在UE中创建骨骼动画

文章目录 创建动画的三种方式修改骨骼动画 创建动画的三种方式 方法一 打开一个已有的动画&#xff0c;左上角“创建资产/创建动画/参考姿势” 这将创建一个默认的A字形的骨骼&#xff0c;不建议这么做 方法二 打开一个已有的动画&#xff0c;左上角“创建资产/创建动画/当前…

[ctfshow web入门] web38

信息收集 过滤多了php和file if(isset($_GET[c])){$c $_GET[c];if(!preg_match("/flag|php|file/i", $c)){include($c);echo $flag;}}else{highlight_file(__FILE__); }解题 更多解法参考 [ctfshow web入门] web37 我们选个最简单的。但是因为php被过滤了我们改用…

汽车CAN总线采样点和采样率详解

写在前面 本篇文章主要讲解在汽车电子中CAN总线采样率的相关知识点,内容涉及CAN波特率、采样点、时间份额、同步跳转宽度以及采样率的计算。 若有相关问题,欢迎评论沟通,共同进步。(*^▽^*) 1、CAN波特率 CAN波特率常规分为250kbps和500kbps,本文章主要以这两个波特率为…

Maven error:Could not transfer artifact

问题描述 当项目从私有仓库下载依赖时&#xff0c;Maven 报错&#xff0c;无法从远程仓库下载指定的依赖包&#xff0c;错误信息如下&#xff1a; Could not transfer artifact com.ding.abcd:zabk-java:pom from/to releases (http://192.1122.101/repory/mavenleases/): 此…

pytorch 反向传播

文章目录 概念计算图自动求导的两种模式 自动求导-代码标量的反向传播非标量变量的反向传播将某些计算移动到计算图之外 概念 核心&#xff1a;链式法则 深度学习框架通过自动计算导数(自动微分)来加快求导。 实践中&#xff0c;根据涉及号的模型&#xff0c;系统会构建一个计…

WindowsPE文件格式入门06.手写最小PE

https://bpsend.net/thread-346-1-1.html 实现目标 实现目标&#xff1a;手写实现不大于 200 Byte大小的PE文件&#xff08;又名&#xff1a;畸形PE/变形PE&#xff09;&#xff0c;要求MessageBox弹框显示一个字符串。实现要点&#xff1a;充分利用空间&#xff0c;在保证遵…

并发编程--互斥锁与读写锁

并发编程–互斥锁与读写锁 文章目录 并发编程--互斥锁与读写锁1. 基本概念2. 互斥锁2.1 基本逻辑2.2 函数接口2.3示例代码12.4示例代码2 3. 读写锁3.1 基本逻辑3.2示例代码 1. 基本概念 互斥与同步是最基本的逻辑概念&#xff1a; 互斥指的是控制两个进度使之互相排斥&#x…

记录第一次使用H5的WebBluetooth完成蓝牙标签打印机的(踩坑)过程

第1步 首先第一步&#xff0c;调试环境必须是https的&#xff0c;由于浏览器的强制安全策略&#xff0c;本地可以采用localhost 第2步 然后&#xff0c;如果打印需要服务UUID&#xff08;Service UUID&#xff09; 和 特征UUID&#xff08;Characteristic UUID&#xff09;&…

【WRF理论第十七期】单向/双向嵌套机制(含namelist.input详细介绍)

WRF运行的单向/双向嵌套机制 准备工作&#xff1a;WRF运行的基本流程namelist.input的详细设置&time_control 设置&domain 嵌套结构&bdy_control 配置部分 namelist 其他注意事项Registry.EM 运行 ARW 嵌套双向嵌套&#xff08;two-way nesting&#xff09;Moving …

React 学习 JSX

APP根组件被index.js渲染到public下的index.html下 JS中写 HTML 代码 渲染列表 条件渲染 复杂条件渲染 事件绑定 传递自定义参数 button标签中写箭头函数引用的格式 自定义参数和事件本身对象都想要的情况