深入浅出:模拟实现 C++ STL 中的 unordered_map 和 unordered_set

news2025/1/10 21:05:07

目录

  1. 引言
  2. 基础知识
    • 散列表
    • 哈希函数
    • 负载因子
  3. 模拟实现 unordered_set
    • 数据结构设计
    • 哈希函数
    • 碰撞解决策略
    • 插入操作
    • 查找操作
    • 删除操作
  4. 模拟实现 unordered_map
    • 键值对存储
    • 插入操作
    • 查找操作
    • 删除操作
  5. 代码示例
  6. 总结

1. 引言

unordered_mapunordered_set 是 C++ 标准模板库 (STL) 中非常强大的容器类型,它们基于散列表实现,提供了平均情况下 O(1) 的查找、插入和删除性能。本文将带你深入了解这两个容器的内部工作原理,并通过具体的 C++ 代码实现来模拟 unordered_map unordered_set

2. 基础知识

2.1 散列表

散列表是一种通过哈希函数将键映射到数组索引的数据结构。它利用哈希函数将键转换为索引,然后通过该索引直接访问数组中的位置。这种方法通常能提供非常快的查找速度。

2.2 哈希函数

哈希函数是将任意长度的输入映射到固定长度输出的过程。一个好的哈希函数应该尽量减少键之间的冲突,并均匀地分布到整个数组范围内。

2.3 负载因子

负载因子是指散列表中元素的数量与桶的数量之比。当负载因子过高时,散列表的性能会下降,因为更多的键会映射到同一个桶上,导致碰撞增多。

3. 模拟实现 unordered_set

3.1 数据结构设计

对于 unordered_set,我们使用一个动态数组来存储链表,每个链表代表一个桶。当发生碰撞时,使用链地址法来解决。

template<typename T>
class UnorderedSet {
public:
    // 构造函数
    UnorderedSet(size_t initialCapacity = 16, float loadFactor = 0.75f) 
        : capacity(initialCapacity), size(0), loadFactorThreshold(loadFactor * initialCapacity) {
        table.resize(capacity);
    }

    // 插入操作
    bool insert(const T& value);

    // 查找操作
    bool find(const T& value) const;

    // 删除操作
    bool erase(const T& value);

private:
    // 重新哈希
    void rehash();

    // 计算哈希值
    size_t hash(const T& value) const;

    // 动态数组
    std::vector<std::list<T>> table;

    // 当前容量
    size_t capacity;

    // 当前元素数量
    size_t size;

    // 负载因子阈值
    size_t loadFactorThreshold;
};

template<typename T>
size_t UnorderedSet<T>::hash(const T& value) const {
    // 简单的哈希函数实现
    static std::hash<T> hasher;
    return hasher(value) % capacity;
}
3.2 哈希函数

使用 C++ 标准库提供的 std::hash 类型来实现哈希函数。

3.3 碰撞解决策略

使用链地址法来解决碰撞问题。当多个键映射到同一个桶时,这些键会被存储在同一个链表中。

3.4 插入操作

插入操作首先计算哈希值,然后检查是否需要重新哈希,最后将元素插入到相应的链表中。

template<typename T>
bool UnorderedSet<T>::insert(const T& value) {
    if (size >= loadFactorThreshold) {
        rehash();
    }

    size_t index = hash(value);
    auto& bucket = table[index];

    // 检查元素是否已存在
    if (std::find(bucket.begin(), bucket.end(), value) != bucket.end()) {
        return false;
    }

    bucket.push_back(value);
    ++size;
    return true;
}
3.5 查找操作

查找操作也使用哈希值定位桶,然后在对应的链表中查找元素。

template<typename T>
bool UnorderedSet<T>::find(const T& value) const {
    size_t index = hash(value);
    const auto& bucket = table[index];

    return std::find(bucket.begin(), bucket.end(), value) != bucket.end();
}
3.6 删除操作

删除操作同样使用哈希值定位桶,然后在对应的链表中删除元素。

template<typename T>
bool UnorderedSet<T>::erase(const T& value) {
    size_t index = hash(value);
    auto& bucket = table[index];

    auto it = std::find(bucket.begin(), bucket.end(), value);
    if (it == bucket.end()) {
        return false;
    }

    bucket.erase(it);
    --size;
    return true;
}

4. 模拟实现 unordered_map

4.1 键值对存储

unordered_mapunordered_set 类似,但存储的是键值对。

template<typename K, typename V>
class UnorderedMap {
public:
    // 构造函数
    UnorderedMap(size_t initialCapacity = 16, float loadFactor = 0.75f) 
        : capacity(initialCapacity), size(0), loadFactorThreshold(loadFactor * initialCapacity) {
        table.resize(capacity);
    }

    // 插入操作
    std::pair<typename std::list<std::pair<K, V>>::iterator, bool> insert(const std::pair<K, V>& value);

    // 查找操作
    typename std::list<std::pair<K, V>>::iterator find(const K& key);

    // 删除操作
    bool erase(const K& key);

private:
    // 重新哈希
    void rehash();

    // 计算哈希值
    size_t hash(const K& key) const;

    // 动态数组
    std::vector<std::list<std::pair<K, V>>> table;

    // 当前容量
    size_t capacity;

    // 当前元素数量
    size_t size;

    // 负载因子阈值
    size_t loadFactorThreshold;
};

template<typename K, typename V>
size_t UnorderedMap<K, V>::hash(const K& key) const {
    static std::hash<K> hasher;
    return hasher(key) % capacity;
}
4.2 插入操作

插入操作与 unordered_set 类似,只是插入的是键值对。

template<typename K, typename V>
std::pair<typename std::list<std::pair<K, V>>::iterator, bool> UnorderedMap<K, V>::insert(const std::pair<K, V>& value) {
    if (size >= loadFactorThreshold) {
        rehash();
    }

    size_t index = hash(value.first);
    auto& bucket = table[index];

    auto it = std::find_if(bucket.begin(), bucket.end(),
                           [&value](const std::pair<K, V>& pair) { return pair.first == value.first; });

    if (it != bucket.end()) {
        return {it, false};
    }

    bucket.push_back(value);
    ++size;
    return {std::prev(bucket.end()), true};
}
4.3 查找操作

查找操作与 unordered_set 类似,只是返回的是键值对的迭代器。

template<typename K, typename V>
typename std::list<std::pair<K, V>>::iterator UnorderedMap<K, V>::find(const K& key) {
    size_t index = hash(key);
    auto& bucket = table[index];

    return std::find_if(bucket.begin(), bucket.end(),
                        [&key](const std::pair<K, V>& pair) { return pair.first == key; });
}
4.4 删除操作

删除操作与 unordered_set 类似,只是删除的是键值对。

template<typename K, typename V>
bool UnorderedMap<K, V>::erase(const K& key) {
    size_t index = hash(key);
    auto& bucket = table[index];

    auto it = std::find_if(bucket.begin(), bucket.end(),
                           [&key](const std::pair<K, V>& pair) { return pair.first == key; });

    if (it == bucket.end()) {
        return false;
    }

    bucket.erase(it);
    --size;
    return true;
}

5. 代码示例

下面是一个简单的测试示例,展示如何使用模拟实现的 UnorderedSetUnorderedMap

#include <iostream>
#include "unordered_set.h"
#include "unordered_map.h"

int main() {
    UnorderedSet<int> uset;
    uset.insert(10);
    uset.insert(20);
    uset.insert(30);
    std::cout << "Contains 20: " << uset.find(20) << std::endl;

    UnorderedMap<int, std::string> umap;
    umap.insert({10, "ten"});
    umap.insert({20, "twenty"});
    umap.insert({30, "thirty"});

    auto it = umap.find(20);
    if (it != umap.end()) {
        std::cout << "Value for 20: " << it->second << std::endl;
    }

    return 0;
}

6. 总结

本文通过模拟实现的方式,深入探讨了 C++ STL 中 unordered_setunordered_map 的内部工作原理。通过理解散列表的工作机制,我们可以更好地利用这些容器提供的高效性能,并在需要时定制自己的哈希函数或碰撞解决策略。

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

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

相关文章

【Python基础】Python运算符

本文收录于 《Python编程入门》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程基础知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、Python 运算符2.1 什么是运算符&#xff1f;2.2 Python算术运算符2.3 Python 比较运算符2.4 Pytho…

大数据-108 Flink 快速应用案例 重回Hello WordCount!方案1批数据 方案2流数据

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

OpenCV绘图函数(6)绘制椭圆函数ellipse()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 画出一个简单的或粗的椭圆弧或者填充一个椭圆扇形。 函数 cv::ellipse 使用更多的参数可以画出椭圆轮廓、填充的椭圆、椭圆弧或填充的椭圆扇形。…

复现很难吗?找我帮你解决烦恼

代码复现&#xff0c;算法复现&#xff0c;文章复现&#xff0c;科研复现 Matlab&#xff0c;Python均可 文献里的算法&#xff0c;方法均可复现&#xff0c; 提供代码改进&#xff0c;模型优化&#xff0c;增加模块&#xff0c;python代做&#xff0c;预测&#xff0c;微调&am…

潮玩宇宙无聊猿斗兽场游戏开发代码示例

明确游戏目标和定位&#xff1a;确定游戏的类型&#xff08;比如是竞技类、策略类等&#xff09;、风格、玩法规则等。设计游戏架构&#xff1a;包括服务器架构、客户端架构、数据库设计等。美术设计&#xff1a;创作游戏中的角色、场景、道具等美术资源。编程实现&#xff1a;…

五分钟本地部署Uptime Kuma运维监控结合内网穿透实现远程访问

文章目录 前言**主要功能**一、前期准备本教程环境为&#xff1a;Centos7&#xff0c;可以跑Docker的系统都可以使用本教程安装。本教程使用Docker部署服务&#xff0c;如何安装Docker详见&#xff1a; 二、Docker部署Uptime Kuma三、实现公网查看网站监控四、使用固定公网地址…

MySQL:简述多版本并发控制MVCC

一、MVCC的概念 1、MVCC 数据库并发场景有三种&#xff0c;分别为&#xff1a; &#xff08;1&#xff09;读读&#xff1a;不存在任何问题&#xff0c;也不需要并发控制。 &#xff08;2&#xff09;读写&#xff1a;有线程安全问题&#xff0c;可能会造成事务隔离性问题&am…

App弱网测试是怎么测试的!

一、网络测试的一般流程 step1&#xff1a;首先要考虑网络正常的情况 ① 各个模块的功能正常可用 ② 页面元素/数据显示正常 step2&#xff1a;其次要考虑无网络的情况 ① APP各个功能在无网络情况下是否可用 ② APP各个页面之间切换是否正常 ③ 发送网络请求时是否会…

算法力扣刷题记录 九十【739. 每日温度】

前言 单调栈第一篇。单调栈解题思路如何&#xff1f; 一、题目阅读 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不会…

2024 Python3.10 系统入门+进阶(十):Python字典及其常用操作详解

目录 一、初始化1.1 {}--直接创建字典1.2 dict()函数--创建字典1.3 fromkeys()方法--创建一个新字典 二、元素访问2.1 使用中括号[]语法2.2 get()方法--获取字典中指定键的值2.3 setdefault()方法--获取字典中指定键的值 三、新增和修改3.1 直接赋值3.2 update()方法--更新字典…

RabbitMQ练习(Routing)

1、RabbitMQ教程 《RabbitMQ Tutorials》https://www.rabbitmq.com/tutorials 2、环境准备 参考&#xff1a;《RabbitMQ练习&#xff08;Hello World&#xff09;》和《RabbitMQ练习&#xff08;Work Queues&#xff09;》。 确保RabbitMQ、Sender、Receiver、Receiver2容器…

人工智能训练师一级(高级技师)、二级(技师)考试指南

随着经济快速发展&#xff0c;人工智能技术在制造业、交通运输、农业、医疗健康、金融服务、物流配送以及城市服务等多个领域得到了广泛的应用。不仅带来产业的转型升级&#xff0c;更是对具备相应技能的人工智能训练师需求的激增。 根据教育部发布的《关于做好职业教育“…

BugKu练习记录:把猪困在猪圈里

题目&#xff1a; 用base64解码 再对应猪圈密码解码&#xff0c;得到答案 t h i s i s p i g p a s s w o r d

house of pig

文章目录 house of pig介绍&#xff1a;利用条件&#xff1a;利用流程&#xff1a; 例题&#xff1a;利用&#xff1a; 总结&#xff1a; house of pig 介绍&#xff1a; House of Pig 是一个将 Tcache Stash Unlink Attack 和 FSOP 结合的攻击&#xff0c;同时使用到了 Larg…

MQ专题:事务消息的实现方式

方案 事务消息投递的过程 step1&#xff1a;开启本地事务step2&#xff1a;执行本地业务step3&#xff1a;消息表t_msg写入记录&#xff0c;status为0&#xff08;待投递到MQ&#xff09;step4&#xff1a;提交本地事务step5&#xff1a;若事务提交成功&#xff0c;则投递消息…

【原创教程】电气电工13:按钮开关指示灯篇

按钮开关在电气电工工作中,看似简单,但是有些细节问题,我们还是要注意的。电气电工工作是一个完整的体系,任何一件事疏忽,都会埋下安全隐患。 首先我们来看下 开关按钮的定义: 按钮开关是指利用按钮推动传动机构,使动触点与静触点按通或断开并实现电路换接的开关。按…

软件测试面试题!收藏起来,每天看一看,月薪20K!

初级测试总结题&#xff01;必背&#xff01;必背&#xff01;必背&#xff01; 1&#xff09;软件的概念&#xff1f; 软件是计算机系统中与硬件相互依存的一部分&#xff0c;包括程序、数据以及与其相关文档的完整集合。 2&#xff09;软件测试的概念&#xff1f; 使用人…

讲透一个强大的算法模型,Transformer

Transformer 模型是一种基于注意力机制的深度学习模型&#xff0c;广泛应用于自然语言处理&#xff08;NLP&#xff09;任务&#xff0c;如机器翻译、文本生成和语义理解。 它最初由 Vaswani 等人在2017年的论文《Attention is All You Need》中提出。它突破了传统序列模型&am…

搬运5款实用工具,帮你更好地完成各种任务

​ 在日常工作和生活中&#xff0c;使用各种工具来提升效率和简化任务变得尤为重要。本文将介绍几款实用的工具&#xff0c;帮助你更好地完成各种任务。 1. 自动化脚本——AutoHotkey ​ AutoHotkey是一款功能强大的自动化脚本编写工具&#xff0c;可以用来自动执行日常任务&…

【网络安全】漏洞挖掘

漏洞描述 Spring框架为现代基于java的企业应用程序(在任何类型的部署平台上)提供了一个全面的编程和配置模型。 Spring Cloud 中的 serveless框架 Spring Cloud Function 中的 RoutingFunction 类的 apply 方法将请求头中的“spring.cloud.function.routing-expression”参数…