C++-第25课-哈希表性能的分析

目录

 一、哈希表概述

1. 什么是哈希表​编辑

2. 哈希表的优点

3. 哈希表的缺点

二、哈希函数

常见哈希函数

三. 哈希冲突的原因和解决方法

一.哈希冲突的原因

二、哈希冲突的解决方法

1. 链表法(Separate Chaining)

2. 开放寻址法(Open Addressing)

四 哈希表的实际应用:C++实现

1. 使用 unordered_set

2. 使用 unordered_map

 五.哈希表与红黑树的性能对比

总结


🌇引言

在计算机科学中,数据结构与算法是解决问题的核心工具。各种数据结构和算法的设计决定了程序的效率和性能。在众多数据结构中,哈希表(Hash Table) 是一个极其重要且高效的结构。它广泛应用于数据库索引、缓存技术、加密算法等领域,并且是很多高级编程语言中内置的基础容器之一。

本文将深入探讨哈希表的原理、哈希函数的设计、哈希冲突的解决策略,并结合C++实现 unordered_setunordered_map 来展示哈希表的实际应用。

 一、哈希表概述

1. 什么是哈希表

哈希表是一种基于数组的数据结构,用于快速地存储和查找数据。它通过一个哈希函数将元素的键值映射到哈希表中的一个位置,从而实现常数时间复杂度 O(1) 的查找和插入操作。

哈希表的基本工作原理如下:

  • 键值对存储:每个元素都有一个“键”与其对应的“值”。
  • 哈希函数:根据“键”计算出该元素的存储位置(哈希值)。
  • 数组存储:将哈希值作为索引,快速存取对应的值。
2. 哈希表的优点
  • 高效查找:对于随机访问、插入和删除操作,哈希表能提供接近 O(1)O(1)O(1) 的时间复杂度,这是因为它能直接通过哈希函数定位到元素的存储位置。
  • 空间利用:哈希表通常比其他结构(如树)更节省空间,尤其是在大规模数据的存储上表现优秀。
3. 哈希表的缺点
  • 内存浪费:为了避免冲突,哈希表通常需要较大的内存空间。
  • 哈希冲突:当不同的键经过哈希函数计算后,映射到同一位置时,会发生哈希冲突,需要通过一定方法来解决。

二、哈希函数

哈希函数是哈希表的核心,它负责将输入的键值映射到一个固定范围内的哈希值。一个好的哈希函数应当具备以下几个特点:

  • 均匀性:哈希值应均匀分布,避免集中在哈希表的某些区域。
  • 效率:哈希函数应当简单、计算速度快。
  • 低碰撞率:哈希函数应尽量减少哈希冲突的发生。
常见哈希函数

哈希函数 的发展已经有很多年历史了,在前辈的实践之下,留下了这些常见的 哈希函数

1、直接定址法(常用)

函数原型:HashI = A * key + B

优点:简单、均匀
缺点:需要提前知道键值的分布情况

适用场景:范围比较集中,每个数据分配一个唯一位置


2、除留余数法(常用)

假设 哈希表 的大小为 m

函数原型:HashI = key % p (p < m)

优点:简单易用,性能均衡
缺点:容易出现哈希冲突,需要借助特定方法解决
适用场景:范围不集中,分布分散的数据


3、平方取中法(了解)

函数原型:HashI = mid(key * key)

适用场景:不知道键值的分布,而位数又不是很大的情况
假设键值为 1234,对其进行平方后得到 1522756,取其中间的三位数 227 作为 哈希值


4、折叠法(了解)

折叠法是将键值从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按 哈希表 表长,取后几位作为散列地址

适用场景:事先不需要知道键值的分布,且键值位数比较多
假设键值为 85673113,分为三部分 856、731、13,求和:1600,根据表长(假设为 100),哈希值 就是 600


5、随机数法(了解)

选择一个随机函数,取键值的随机函数值为它的 哈希值

函数原型:HashI = rand(key)
其中 rand 为随机数函数

适用场景:键值长度不等时
哈希函数 还有很多很多种,最终目的都是为了计算出重复度低的 哈希值

最常用的是 直接定址法 和 除留余数法


三. 哈希冲突的原因和解决方法

哈希表是一种高效的数据结构,广泛应用于各种算法中,如查找、插入和删除操作等。然而,哈希表并非没有缺点,最常见的问题之一就是哈希冲突。哈希冲突发生在多个元素被映射到哈希表中的同一个位置时。理解哈希冲突的原因以及解决方法对于优化哈希表的性能至关重要。

一.哈希冲突的原因

哈希冲突的产生主要由以下几个原因导致:

看下方的简图😀😀:

 

1. 哈希函数的设计问题

哈希函数的主要任务是将输入数据(如字符串或整数)映射到哈希表的索引上。理想的哈希函数应该能够均匀地分布所有可能的输入值。然而,在实际应用中,哈希函数可能会出现设计不当的情况,例如:

  • 哈希函数映射范围过小,导致许多不同的输入值映射到同一个索引。
  • 哈希函数的输出分布不均匀,导致哈希表中某些区域频繁发生冲突。

2. 输入数据的特点

哈希冲突的发生也可能与输入数据本身的特性有关。例如,如果多个数据项本身非常相似(如有相同的前缀或后缀),即使哈希函数设计得当,它们也可能被映射到相同的哈希桶。

3. 哈希表的大小和负载因子

哈希表的大小决定了它能容纳多少元素。当哈希表的负载因子(即哈希表中元素的数量与表的容量之比)过高时,哈希冲突的概率也会增加。即使哈希函数设计得很好,如果哈希表容量不足以存储所有元素,哈希冲突不可避免。


二、哈希冲突的解决方法

为了解决哈希冲突,通常有两种主要的方法:开放寻址法链表法。每种方法都有其优缺点,适用于不同的场景。

1. 链表法(Separate Chaining)

链表法是最常见的哈希冲突解决方案。其基本思路是,在哈希表的每个位置上,存储一个链表或其他数据结构,用来存储发生冲突的多个元素。

具体步骤

  • 每当发生冲突时,将新的元素插入到该位置的链表中。
  • 查找时,首先通过哈希函数找到对应的桶,再遍历桶中的链表进行查找。

优点

  • 解决哈希冲突的简单性:链表法能够很自然地处理哈希冲突。
  • 动态扩展性:如果负载因子过高,链表的长度会增加,但不至于导致性能大幅下降。

缺点

  • 空间浪费:每个桶都需要存储一个额外的数据结构(如链表),这可能导致额外的内存开销。
  • 查找效率下降:如果链表过长,查找操作的效率就会受到影响,最坏情况下变成 O(n)。

2. 开放寻址法(Open Addressing)

开放寻址法的基本思想是,当发生冲突时,哈希表会尝试找到一个空桶来存储冲突的元素,而不是使用链表存储所有冲突的元素。常见的开放寻址法有以下几种:

线性探测(Linear Probing)

线性探测是一种常见的开放寻址方法,当发生冲突时,哈希表会按顺序探测下一个位置,直到找到一个空桶为止。具体步骤如下:

  • 计算哈希值,如果位置已经被占用,则尝试当前位置后的桶(即加1、加2、加3…)。

优点

  • 实现简单,直接。
  • 空间效率较高,因为不需要额外的数据结构。

缺点

  • 聚集问题:线性探测会导致“聚集”现象,即多个元素集中在一小段区域内,导致查找效率降低。
  • 删除操作复杂:删除一个元素时,可能会导致查找操作失败,需要特别处理。

 二次探测(Quadratic Probing)

二次探测是一种改进版的线性探测方法。当发生冲突时,探测过程遵循一个二次方程(如加 1^2、2^2、3^2…)。

优点

  • 避免了线性探测的聚集问题。

缺点

  • 仍然有聚集现象(虽然比线性探测要轻微)。

可能导致表的大小不合适,影响性能。


四 哈希表的实际应用:C++实现

C++标准库提供了基于哈希表实现的容器,如 unordered_setunordered_map,它们是无序的集合和映射容器。与 set map 的区别在于,unordered_set unordered_map 使用哈希表作为底层实现,因此查找、插入和删除操作具有常数时间复杂度。

1. 使用 unordered_set

unordered_set 是一个哈希表实现的集合,存储的元素是唯一的,不允许重复。

#include <iostream>
#include <unordered_set>int main() {std::unordered_set<int> mySet = {1, 2, 3, 4, 5};// 插入元素mySet.insert(6);// 查找元素if (mySet.find(3) != mySet.end()) {std::cout << "Found 3!" << std::endl;}// 删除元素mySet.erase(4);// 打印元素for (int element : mySet) {std::cout << element << " ";}std::cout << std::endl;return 0;
}

2. 使用 unordered_map

unordered_map 是一个哈希表实现的映射容器,存储键值对。

#include <iostream>
#include <unordered_map>int main() {std::unordered_map<std::string, int> myMap;// 插入元素myMap["Alice"] = 25;myMap["Bob"] = 30;// 查找元素if (myMap.find("Alice") != myMap.end()) {std::cout << "Alice's age: " << myMap["Alice"] << std::endl;}// 删除元素myMap.erase("Bob");// 打印键值对for (const auto& pair : myMap) {std::cout << pair.first << ": " << pair.second << std::endl;}return 0;
}

 五.哈希表与红黑树的性能对比

下面是性能测试代码,包含 大量重复、部分重复、完全有序 三组测试用例,分别从 插入、查找、删除 三个维度进行对比

注:测试性能用的是 Release 版,这里的基础数据量为 100 w

#include <iostream>
#include <vector>
#include <set>
#include <unordered_set>using namespace std;int main()
{const size_t N = 1000000;unordered_set<int> us;set<int> s;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; ++i){//v.push_back(rand());	//大量重复//v.push_back(rand()+i);	//部分重复//v.push_back(i);	//完全有序}size_t begin1 = clock();for (auto e : v){s.insert(e);}size_t end1 = clock();cout << "set insert:" << end1 - begin1 << endl;size_t begin2 = clock();for (auto e : v){us.insert(e);}size_t end2 = clock();cout << "unordered_set insert:" << end2 - begin2 << endl;size_t begin3 = clock();for (auto e : v){s.find(e);}size_t end3 = clock();cout << "set find:" << end3 - begin3 << endl;size_t begin4 = clock();for (auto e : v){us.find(e);}size_t end4 = clock();cout << "unordered_set find:" << end4 - begin4 << endl << endl;cout << s.size() << endl;cout << us.size() << endl << endl;;size_t begin5 = clock();for (auto e : v){s.erase(e);}size_t end5 = clock();cout << "set erase:" << end5 - begin5 << endl;size_t begin6 = clock();for (auto e : v){us.erase(e);}size_t end6 = clock();cout << "unordered_set erase:" << end6 - begin6 << endl << endl;return 0;
}

 插入大量重复数据

 

插入数据 大量重复 ---- 结果:

  • 插入:哈希 比 红黑 快 88%
  • 查找:哈希 比 红黑 快 100%
  • 删除:哈希 比 红黑 快 37%

插入部分重复数据

插入数据 部分重复 ---- 结果:

  • 插入:哈希 与 红黑 差不多
  • 查找:哈希 比 红黑 快 100%
  • 删除:哈希 比 红黑 快 41%

插入大量重复数据

插入数据 完全有序 ---- 结果:

  • 插入:哈希 比 红黑 慢 52%
  • 查找:哈希 比 红黑 快 100%
  • 删除:哈希 比 红黑 慢 58%

总的来说,在数据 随机 的情况下,哈希各方面都比红黑强,在数据 有序 的情况下,红黑更胜一筹.

综上所述🌼🌼:

1. 操作性能差异

操作哈希表平均时间复杂度红黑树平均时间复杂度
查找O(1)O(1)O(1)O(log⁡n)O(\log n)O(logn)
插入O(1)O(1)O(1)O(log⁡n)O(\log n)O(logn)
删除O(1)O(1)O(1)O(log⁡n)O(\log n)O(logn)

从表中可以看出,哈希表的平均性能优于红黑树,尤其在查找和插入操作中,其常数时间复杂度具有明显优势。然而,红黑树的性能更为稳定,即使在最坏情况下,其时间复杂度依然为 O(log⁡n)O(\log n)O(logn)。

2. 空间效率对比

  • 哈希表:通常需要较大的内存来避免哈希冲突,因此在存储稀疏数据时可能造成一定的空间浪费。
  • 红黑树:作为平衡二叉树,其内存利用率较高,适合存储密集数据。

3. 数据顺序支持

哈希表中的数据存储是无序的,无法直接支持范围查询或按顺序遍历。而红黑树则天然支持有序性操作,如查找区间范围、按序遍历等。


总结

哈希表是现代计算机科学中不可或缺的数据结构,它通过哈希函数实现快速的数据存取,并广泛应用于各个领域。本文通过对哈希表的介绍,深入探讨了哈希函数的设计、哈希冲突的解决方法以及 C++ 中的实现,旨在帮助读者更好地理解和使用这一高效的数据结构。

在实际应用中,我们不仅要选择合适的哈希函数,还要灵活运用冲突解决策略,以确保哈希表能够高效地工作。在性能

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HDMI数据传输三种使用场景

视频和音频的传输 在HDMI传输音频中有3种方式进行传输&#xff0c;第一种将音频和视频信号被嵌入到同一数据流中&#xff0c;通过一个TMDS&#xff08;Transition Minimized Differential Signaling&#xff09;通道传输。第二种ARC。第三张种eARC。这三种音频的传输在HDMI线中…

LCR 184.设计自助结算系统

1.题目要求: 2.题目代码: class Checkout { public:deque<int> array;Checkout() {array.clear();}//求最大值int get_max() {if(array.size() 0){return -1;}else{vector<int> temp(array.begin(),array.end());vector<int> :: iterator it max_element…

Vue3-小兔鲜项目出现问题及其解决方法(未写完)

基础操作 &#xff08;1&#xff09;使用create-vue搭建Vue3项目 要保证node -v 版本在16以上 &#xff08;2&#xff09;添加pinia到vue项目 npm init vuelatest npm i pinia //导入creatPiniaimport {createPinia} from pinia//执行方法得到实例const pinia createPinia()…

VUE:基于MVVN的前端js框架

文章目录 vue框架v-show vue框架 注意是 先写函数名&#xff0c;再写function。 handle:function (){}下面是错误的 function:handle(){}3 v-show 本质上等于号后面还是判断条件&#xff0c;所以不能写赋值语句&#xff0c;下面是正确的 下面是错误的 v-show " ge…

金融数据中心容灾“大咖说” | Veritas的“高举高打”之道

中国人民银行发布的《金融数据中心容灾建设指引》&#xff08;JR/T 0264—2024&#xff09;已于2024年7月29日正式实施。这一金融行业标准对金融数据中心容灾建设中的“组织保障、需求分析、体系规划、建设要求、运维管理”进行了规范和指导。面对不断增加的各类网络、业务、应…

VUE字符串转日期加天数

文章为本新手菜鸡的问题记录&#xff0c;如有错误和不足还行大佬指正 文章目录 问题描述解决方法 问题描述 得到一串字符串的日期&#xff0c;因为不是规范的日期格式&#xff0c;无法使用moment().add()方法&#xff0c;那么如何实现增加天数的操作&#xff1f; 解决方法 1…

高校企业数据挖掘平台推荐

TipDM数据挖掘建模平台是由广东泰迪智能科技股份有限公司自主研发打造的可视化、一站式、高性能的数据挖掘与人工智能建模服务平台&#xff0c;致力于为使用者打通从数据接入、数据预处理、模型开发训练、模型评估比较、模型应用部署到模型任务调度的全链路。平台内置丰富的机器…

《TCP/IP网络编程》学习笔记 | Chapter 14:多播与广播

《TCP/IP网络编程》学习笔记 | Chapter 14&#xff1a;多播与广播 《TCP/IP网络编程》学习笔记 | Chapter 14&#xff1a;多播与广播多播多播的数据传输方式和特点路由&#xff08;Routing&#xff09;和 TTL&#xff08;Time to Live&#xff0c;生存时间&#xff09;&#xf…

利用 TensorFlow Profiler:在 AMD GPU 上优化 TensorFlow 模型

TensorFlow Profiler in practice: Optimizing TensorFlow models on AMD GPUs — ROCm Blogs 简介 TensorFlow Profiler 是一组旨在衡量 TensorFlow 模型执行期间资源利用率和性能的工具。它提供了关于模型如何与硬件资源交互的深入见解&#xff0c;包括执行时间和内存使用情…

游戏引擎学习第15天

视频参考:https://www.bilibili.com/video/BV1mbUBY7E24 关于游戏中文件输入输出&#xff08;IO&#xff09;操作的讨论。主要分为两类&#xff1a; 只读资产的加载 这部分主要涉及游戏中用于展示和运行的只读资源&#xff0c;例如音乐、音效、美术资源&#xff08;如 3D 模型和…

(二)Ubuntu22.04+Stable-Diffusion-webui AI绘画 中英双语插件安装

一、说明 看情况添加 二、双语安装 双语插件 https://github.com/journey-ad/sd-webui-bilingual-localization 中文语言包 https://github.com/dtlnor/stable-diffusion-webui-localization-zh_CN 先装中文语言包 错误&#xff1a;AssertionError: extension access disable…

UE5 DownloadImage加载jpg失败的解决方法

DownloadImage加载jpg失败的解决方法 现象解决方案具体方法 现象 用UE自带的 DownloadImage 无法下载成功&#xff0c;从 failure 引脚出来。 接入一个由监控器自动保存起的图像&#xff0c;有些可以正常加载成功&#xff0c;有些无法加载成功。 经调查问题出现在&#xff0c;…

Elasticsearch 中的热点以及如何使用 AutoOps 解决它们

作者&#xff1a;来自 Elastic Sachin Frayne 探索 Elasticsearch 中的热点以及如何使用 AutoOps 解决它。 Elasticsearch 集群中出现热点的方式有很多种。有些我们可以控制&#xff0c;比如吵闹的邻居&#xff0c;有些我们控制得较差&#xff0c;比如 Elasticsearch 中的分片分…

Statsmodels之OLS回归

目录 Statsmodels基本介绍OLS 回归实战实战1&#xff1a;实战2&#xff1a; Statsmodels基本介绍 Statsmodels 是 Python 中一个强大的统计分析包&#xff0c;包含了回归分析、时间序列分析、假设检验等等的功能。Statsmodels 在计量的简便性上是远远不及 Stata 等软件的&…

【接口封装】—— 1、加载样式表

函数定义 static void loadStyleSheet(QWidget* widget, const QString &fileName,const QString& otherStyleQString());&#xff08;头文件&#xff09;&#xff1a; #include <qfile.h> #include <QWidget> 源文件: void CommonUtils::loadStyleSheet(…

AI、VR与空间计算:教育和文旅领域的数字转型力量

在这个数字技术高速发展的时代&#xff0c;AI、VR技术及大空间计算技术&#xff0c;已成为推动多个行业革新的强劲动力。近日&#xff0c;世优科技推出了最新研发的VR大空间产品《山海经》&#xff0c;这一全新的沉浸式体验项目不仅重新定义了观展方式&#xff0c;还为文化旅游…

AWTK 最新动态:支持鸿蒙系统(HarmonyOS Next)

HarmonyOS是全球第三大移动操作系统&#xff0c;有巨大的市场潜力&#xff0c;在国产替代的背景下&#xff0c;机会多多&#xff0c;AWTK支持HarmonyOS&#xff0c;让AWTK开发者也能享受HarmonyOS生态的红利。 AWTK全称为Toolkit AnyWhere&#xff0c;是ZLG倾心打造的一套基于C…

数据库、数据仓库、数据湖、数据中台、湖仓一体的概念和区别

数据库、数据仓库、数据湖、数据中台和湖仓一体是数据管理和分析领域的不同概念&#xff0c;各自有不同的特点和应用场景。以下是它们的主要区别&#xff1a; 1. 数据库&#xff08;Database&#xff09; 定义&#xff1a;结构化的数据存储系统&#xff0c;用于高效地存储、检…

Linux运维篇-iscsi存储搭建

目录 概念实验介绍环境准备存储端软件安装使用targetcli来管理iSCSI共享存储 客户端软件安装连接存储 概念 iSCSI是一种在Internet协议上&#xff0c;特别是以太网上进行数据块传输的标准&#xff0c;它是一种基于IP Storage理论的存储技术&#xff0c;该技术是将存储行业广泛…

Django一分钟:django中收集关联对象关联数据的方法

场景&#xff1a;我有一个模型&#xff0c;被其它多个模型关联&#xff0c;我配置了CASCADE级联删除&#xff0c;我想要告知用户删除该实例之后&#xff0c;哪些关联数据将会被一同删除。 假设我们当前有这样一组模型&#xff1a; class Warehouse(models.Model):""…