算法与数据结构——哈希表

news/2024/11/16 16:09:35/文章来源:https://www.cnblogs.com/1873cy/p/18382517

哈希表

哈希表(hash table),又称散列表,它通过建立键key与值value之间的映射,实现高效的元素查询。具体而言,我们向哈希表中输入一个键key,则可以在O(1)时间内获取对应的值value

除哈希表外,数组和链表也可以实现查询功能,他们的效率对比如下表:

  • 添加元素:仅需将元素添加至数组(链表)的尾部即可,使用O(1)时间。
  • 查询元素:由于数组(链表)是乱序的,因此需要遍历其中的所有元素,使用O(n)时间。
  • 删除元素:需要先查询到元素,再从数组(链表)中删除,使用O(n)时间。
  数组 链表 哈希表
查找元素 O(n) O(n) O(1)
添加元素 O(1) O(1) O(1)
删除元素 O(n) O(n) O(1)

观察发现,在哈希表中进行增删改查操作的时间复杂度都是O(1),非常高效。

哈希表常用操作

哈希表的常见操作包括:初始化、查询操作、添加键值对和删除键值对等,C++中提供了现成的哈希表类:

/*初始化哈希表*/unordered_map<int, string> map;/*添加操作*/// 在哈希表中添加键值对(key,value)map[123] = "张三";map[110] = "李四";map[188] = "王五";/*查询操作*/// 向哈希表中输入键 key 得到值 valuestring name = map[188];cout << "姓名:" << name << endl;/*删除操作*/// 在哈希表中根据键key删除键值对map.erase(188);
}

哈希表有三种常用的遍历方式:遍历键值对、遍历键和遍历值。

 

	/*遍历哈希表*/// 遍历键值对 kay -> valuefor (auto kv : map){cout << kv.first << " -> " << kv.second << endl;}// 使用迭代器遍历key -> valuefor (auto iter = map.begin(); iter != map.end(); iter++){cout << iter->first << " -> " << iter->second << endl;}

哈希表简单实现

先考虑最简单的情况,仅用一个数组来实现哈希表。在哈希表中,我们将数组中的每个空位称为(bucket),每个桶可存储一个键值对。因此查询操作就是找到key对应的桶,并在桶中获取value。

那么,如何基于key定位到对应的桶呢? 这是通过哈希函数(hash function)实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有的key,输出空间是所有的桶(数组索引)。换句话说,输入一个key,我们可以通过哈希函数得到该key对应的键值对在数组中的存储位置。

输入一个key,哈希函数的计算过程分为以下两步。

  1. 通过某种哈希算法hash()计算得到哈希值。
  2. 将哈希值对桶数量(数组长度)capacity取模,从而获取该key对应的数组索引index。

index = hash(key) % capacity

随后我们就可以利用index在哈希表中访问对应的桶,从而获取value。

设数组长度capacity = 100、哈希算法hash(key) = key,易得哈希函数为key % 100。下图以key学号和value姓名为例,展示了哈希函数的工作原理。

以下代码实现了一个简单的哈希表。其中,我们将key和value封装成一个Pair类,以表示键值对。

/*键值对*/
struct Pair{int key;string val;Pair(int key, string val){this->key = key;this->val = val;}
};
/*基于数组实现的哈希表*/
class ArrayHashMap{
private:vector<Pair*> buckets;
public:ArrayHashMap(){// 初始化数组,包含100个桶buckets = vector<Pair*>(100);}~ArrayHashMap(){// 释放内存for (const auto &bucket : buckets){delete bucket;}buckets.clear();}/*哈希函数*/int hashFunc(int key){return key % 100;}/*查询操作*/string get(int key){int index = hashFunc(key);Pair *pair = buckets[index];if (pair == nullptr)return "";return pair->val;}/*添加操作*/void put(int key, string val){Pair *pair = new Pair(key, val);int index = hashFunc(key);buckets[index] = pair;		}/*删除操作*/void remove(int key){int index = hashFunc(key);/*迭代器方式删除auto iter = buckets.begin();iter += index;buckets.erase(iter);*/// 置空方式删除delete buckets[index];buckets[index] = nullptr;}/*获取所有键值对*/vector<Pair*> pairSet(){vector<Pair*> pair_set;for (Pair* pair:buckets){if (pair)pair_set.push_back(pair);}return pair_set;}/*获取所有键*/vector<int> keySet(){vector<int> key_set;for (Pair* pair : buckets){if (pair)key_set.push_back(pair->key);}return key_set;}/*获取所有值*/vector<string> valueSet(){vector<string> val_set;for (Pair* pair : buckets){if (pair)val_set.push_back(pair->val);}return val_set;}/*打印哈希表*/void print(){for (Pair* pair : pairSet()){cout << pair->key << "->" << pair->val << endl;}}
};

哈希冲突与扩容

从本质上看,哈希函数的作用是将所有key构成的输入空间映射到数组所有索引构成的输出空间,而输入空间往往远大于输出空间。因此,理论上一定存在“多个输入对应相同输出”的情况

对于上述示例中的哈希函数,当输入的key后两位相同时,哈希函数的输出结果也相同。例如,查询学号为12836和20336的两个学生时,我们经哈希函数得到的结果均为36。

如图所示,两个学号指向了同一个姓名,这显然是不对的。这样多个输入对应同一输出的情况称为哈希冲突(hash collision)

容易想到,哈希表容量n越大,多个key被分配到同一个桶中的概率越低,冲突就越少。因此,我们可以通过扩容哈希表来减少哈希冲突

如图所示,扩容前键值对(136,A)和(236,D)发生冲突,扩容后冲突消失。

类似于数组扩容,哈希表扩容需要将所有键值对从原哈希表迁移至新哈希表,非常耗时;并且由于哈希表容量capacity改变,我们需要通过哈希函数来重新计算所有键值对的存储位置,这进一步增加了扩容过程的计算开销。为此,编程语言通常会预留足够大的哈希表容量,防止频繁扩容。

负载因子(load factor)是哈希表的一个重要概念,其定义为哈希表的元素数量除以桶数量,用于衡量哈希冲突的严重程度,也常作为哈希表扩容的触发条件。例如在java中,当负载因子超过0.75时,系统会将哈希表扩容至原先的2倍。

 

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

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

相关文章

Android systrace环境的搭建和使用

一、systrace简介 Systrace 是 Android4.1 中新增的性能数据采样和分析工具。它可帮助开发者收集 Android 关键子系统(如 SurfaceFlinger/SystemServer/Kernel/Input/Display 等 Framework 部分关键模块、服务,View系统等)的运行信息,从而帮助开发者更直观的分析系统瓶颈,…

threeJs 修改TransformControls的显示位置

有的时候模型的原点不是自身中心而是在场景的[0, 0, 0]位置这个时候想要让TransformControls的位置显示在模型的中心目前找的的处理方式是修改源码 找到updateMatrixWorld方法 updateMatrixWorld () {...for ( let i = 0; i < handles.length; i ++ ) {...if ( this.mode ==…

对于初创电商公司来说,选择API测试工具时应该考虑哪些因素?

成本效益: 初创公司通常预算有限,因此需要考虑工具的购买成本或订阅费用。 寻找提供免费版本或社区版的工具,这些版本可能已经满足基本需求。 易用性: 选择学习曲线较低的工具,以便团队成员可以快速上手。 界面友好和直观的工具可以减少培训时间和成本。 功能性: 确保所选…

confluence

我是有postgres 的文件了 所以我需要重新创建个容器docker run -itd -p 8090:8090 \--name confluence \-e JVM_MINIMUM_MEMORY=4096m \-e JVM_MAXIMUM_MEMORY=4096m \-v /data/confluence:/opt/atlassian/confluence-data \confluence:8.7.1一、部署confluence和postgresql 下…

KepServer 右下角小图标消失,导致无法配置。

描述:右下角小图标消失,导致无法配置。 解决: 重新打开 KEPServerEX 6 Administration

长期埋设,准确测量!GEORMxxxx振弦式钢筋计为岩土工程提供可靠的应力监测

长期埋设,准确测量!GEORMxxxx振弦式钢筋计为岩土工程提供可靠的应力监测GEORMxxxx型钢筋计,能够同步监测混凝土结构内部的温度,而无需进行温度修正。其量程更大,可达到300MPa的拉压应力幅度。GEORMxxxx型钢筋计广泛适用于各类混凝土工程和深基坑开挖安全监测中,可测量混凝…

好用的电商API接口测试工具有什么推荐吗?

电商API接口测试工具推荐:提升开发效率,保障数据质量在电商领域,API接口的稳定性和可靠性至关重要。选择合适的测试工具可以帮助开发者快速发现问题,优化接口性能,从而提升用户体验和业务效率。本文将推荐几款好用的电商API接口测试工具,并探讨它们的特点和优势。 一、Po…

React 高德地图 进京证 (二)

上回书说到,躲开摄像头的基本功能实现了,但有三个核心问题: (1)速度慢 (2)距离远易失败 (3)地图限制 第一个问题:较为简单,把几千个摄像头按行政区划分好带上编号,在路线分段避让时按片儿计算,综合测试速度提升了50%。 //找到每段step途径的 let wayDistrictsCame…

技术如何通过API接口获取自己想要同款商品的数据

确定数据源: 首先,你需要确定哪些平台或服务提供商提供了你感兴趣的商品数据。例如,电商平台、品牌商、市场调研公司等。 了解API文档: 访问提供商的开发者门户网站,阅读API文档。文档会详细介绍如何使用API,包括请求的URL、所需的参数、认证方式、请求限制等。 注册和获…

【C#】数组转置

【需求】 现有一个需求,3行4列的从左到右从上到下的数组,转成4行3列,如图所示: 【实现方法】 通过C#编码实现,两种方法: 第一种方法:public double[] transpose(double[] src, int w, int h){double[] dst = null;if (src == null || src.Length != w * h || w == 0 || …

26-数据增广

随即水平翻转:torchvision.transforms.RandomHorizontalFlip() 上下随机翻转:torchvision.transforms.RandomVerticalFlip()) 随机剪裁,剪裁后的大小为(200,200),(0.1,1)使得随即剪裁原始图片的10%到100%区域里的大小,ratio=(0.5,2)使得高宽比为2:1,torchvision.transfor…

Python-解三元一次方程(Part.2)

一、需要解的方程组为:x + y + z = 26 x - y = 1 2x - y + z = 18 二、下面进入代码实现: 1、导入Sympy库中的符号、方程和求解函数from sympy import symbols, Eq, solve 2、定义变量x, y, z = symbols(x y z) 3、定义方程组# 方程1:x + y + z = 26 eq1 = Eq(x + y + z,…