面试之快速学习STL-无序关联式容器

news2024/11/24 14:22:43
  1. 和关联式容器一样,无序容器也使用键值对(pair 类型)的方式存储数据。不过,本教程将二者分开进行讲解,因为它们有本质上的不同:
    关联式容器的底层实现采用的树存储结构,更确切的说是红黑树结构;
    无序容器的底层实现采用的是哈希表的存储结构。
  2. C++ STL 底层采用哈希表实现无序容器时,会将所有数据存储到一整块连续的内存空间中并且当数据存储位置发生冲突时,解决方法选用的是“链地址法”(又称“开链法”)
  3. 基于底层实现采用了不同的数据结构,因此和关联式容器相比,无序容器具有以下 2 个特点:
    a. 无序容器内部存储的键值对是无序的,各键值对的存储位置取决于该键值对中的键,
    和关联式容器相比,无序容器擅长通过指定键查找对应的值(平均时间复杂度为 O(1))
    b. 但对于使用迭代器遍历容器中存储的元素,无序容器的执行效率则不如关联式容器

unordered_map容器

模版

template 
< class Key,
  class T,
  class Hash = hash<Key>, //容器内部存储键值对所用的哈希函数
  class Alloc = allocator<pair<const Key, T>> //判断各个键值对键相同的规则,默认情况下,使用 STL 标准库中提供的 equal_to<key> 规则,该规则仅支持可直接用 == 运算符做比较的数据类型。
>
    std::unordered_map<std::string, std::string> umap;
    umap.emplace("Python教程","http://c.biancheng.net/python/");
    umap.emplace("Java教程", "http://c.biancheng.net/java/");
    umap.emplace("Linux教程", "http://c.biancheng.net/linux/");
    
    //遍历
    for(auto it = umap.begin(); it != umap
        .end(); ++it) {
            std::cout << it->first << " " << it->second << std::endl;
        }
    /*
     Linux教程 http://c.biancheng.net/linux/
     Java教程 http://c.biancheng.net/java/
     Python教程 http://c.biancheng.net/python/
     */

这些内容都比较无聊,看点别的

STL无序容器底层实现原理(深度剖析)

  1. C++ STL 标准库中,不仅是 unordered_map 容器,所有无序容器的底层实现都采用的是哈希表存储结构。更准确地说,是用“链地址法”(又称“开链法”)解决数据存储位置发生冲突的哈希表,整个存储结构如图 :

在这里插入图片描述

  • 其中,Pi 表示存储的各个键值对。
  • 可以看到,当使用无序容器存储键值对时,会先申请一整块连续的存储空间,但此空间并不用来直接存储键值对,而是存储各个链表的头指针,各键值对真正的存储位置是各个链表的节点。
  • TL 标准库通常选用 vector 容器存储各个链表的头指针。有意思~
  • 不仅如此,在 C++ STL 标准库中,将图 1 中的各个链表称为桶(bucket)每个桶都有自己的编号(从 0 开始)。当有新键值对存储到无序容器中时,整个存储过程分为如下几步:
  • 将该键值对中键的值带入设计好的哈希函数,会得到一个哈希值(一个整数,用 H 表示);
  • 将 H 和无序容器拥有桶的数量 n 做整除运算(即 H % n),该结果即表示应将此键值对存储到的桶的编号;
  • 建立一个新节点存储此键值对,同时将该节点链接到相应编号的桶上。

负载因子(load factor)

负载因子 = 容器存储的总键值对 / 桶数

  1. 默认情况下,无序容器的最大负载因子为 1.0。如果操作无序容器过程中,使得最大复杂因子超过了默认值,则容器会自动增加桶数,并重新进行哈希,以此来减小负载因子的值。
  2. 需要注意的是,此过程会导致容器迭代器失效,但指向单个键值对的引用或者指针仍然有效
  3. 这也就解释了,为什么我们在操作无序容器过程中,键值对的存储顺序有时会“莫名”的发生变动。

成员方法 功能

bucket_count() 返回当前容器底层存储键值对时,使用桶的数量。
max_bucket_count() 返回当前系统中,unordered_map 容器底层最多可以使用多少个桶。
bucket_size(n) 返回第 n 个桶中存储键值对的数量。
bucket(key) 返回以 key 为键的键值对所在桶的编号。
load_factor() 返回 unordered_map 容器中当前的负载因子。
max_load_factor() 返回或者设置当前 unordered_map 容器的最大负载因子。
rehash(n) 尝试重新调整桶的数量为等于或大于 n 的值。如果 n 大于当前容器使用的桶数,则该方法会是容器重新哈希,该容器新的桶数将等于或大于 n。反之,如果 n 的值小于当前容器使用的桶数,则调用此方法可能没有任何作用。
reserve(n) 将容器使用的桶数(bucket_count() 方法的返回值)设置为最适合存储 n 个元素的桶数。
hash_function() 返回当前容器使用的哈希函数对象。

//创建空的umap1
    std::unordered_map<std::string, std::string> umap1;
    std::cout << "umap初始捅数 :" << umap1.bucket_count()<<std::endl;
    std::cout << "umap初始负载因子 :" << umap1.load_factor() << std::endl;
    std::cout << "umap 最大负载因子 : " << umap1.max_load_factor() << std::endl;
    /*
     umap初始捅数 :0
     umap初始负载因子 :0
     umap 最大负载因子 : 1
     */
    //设置 umap 使用最适合存储 9 个键值对的桶数
    umap1.reserve(9);
    std::cout << "umap新捅数 :" << umap1.bucket_count()<<std::endl;
    std::cout << "umap新负载因子 :" << umap1.load_factor() << std::endl;
    /*
     umap新捅数 :11
     umap新负载因子 :0
     */
    umap1["Python教程"]   = "http://c.biancheng.net/python/";
    umap1["Java教程"]     = "http://c.biancheng.net/java/";
    umap1["Linux教程"]    = "http://c.biancheng.net/linux/";
    
    std::cout << "umap新捅数 :" << umap1.bucket_count()<<std::endl;
    std::cout << "umap新负载因子 :" << umap1.load_factor() << std::endl;
    
    //调用 bucket() 获取指定键值对位于桶的编号
    std::cout <<  "以\"Python教程\"为键的键值对,位于桶的编号为:" << umap1.bucket("Python教程") << std::endl;
    
    //自行计算某键值对位于哪个桶
    auto fn = umap1.hash_function();
    std::cout << "计算以\"Python教程\"为键的键值对,位于桶的编号为:" << fn("Python教程") % (umap1.bucket_count()) << std::endl;

关于哈希表

  1. 常用的哈希函数的构造方法有 6 种:直接定址法、数字分析法、平方取中法、折叠法、除留余数法和随机数法。
  2. 直接定址法

H(key)= key 或者 H(key)=a * key + b

  1. 数字分析法: 果关键字由多位字符或者数字组成,就可以考虑抽取其中的 2 位或者多位作为该关键字对应的哈希地址,在取法上尽量选择变化较多的位,避免冲突发生。
    在这里插入图片描述

  2. 平方取中法是对关键字做平方操作,取中间得几位作为哈希地址。此方法也是比较常用的构造哈希函数的方法。

例如关键字序列为{421,423,436},对各个关键字进行平方后的结果为{177241,178929,190096},则可以取中间的两位{72,89,00}作为其哈希地址。

  1. 折叠法 是将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。此方法适合关键字位数较多的情况。

  1. 除留余数法:若已知整个哈希表的最大长度 m,可以取一个不大于 m 的数 p,然后对该关键字 key 做取余运算,即:

H(key)= key % p。

处理冲突的方法

  1. 开放定址法

H(key)=(H(key)+ d)MOD m(其中 m 为哈希表的表长,d 为一个增量)

  1. 当得出的哈希地址产生冲突时,选取以下 3 种方法中的一种获取 d 的值,然后继续计算,直到计算出的哈希地址不在冲突为止,这 3 种方法为:
  • 线性探测法:d=1,2,3,…,m-1
  • 二次探测法:d=12,-12,22,-22,32,…
  • 伪随机数探测法:d=伪随机数
  1. 再哈希法
    当通过哈希函数求得的哈希地址同其他关键字产生冲突时,使用另一个哈希函数计算,直到冲突不再发生。

  2. 链地址法
    将所有产生冲突的关键字所对应的数据全部存储在同一个线性链表中。例如有一组关键字为{19,14,23,01,68,20,84,27,55,11,10,79},其哈希函数为:H(key)=key MOD 13,使用链地址法所构建的哈希表如图 3 所示:

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

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

相关文章

2023-08-17 Untiy进阶 C#知识补充8——C#中的日期与时间

文章目录 一、名词概念二、DateTime三、TimeSpan 一、名词概念 &#xff08;一&#xff09;格里高利历 ​ 格里高利历一般指公元&#xff0c;即公历纪年法。目前我们所说公历&#xff0c;就是格里高利历。 ​ 比如 2022 年就是从公元元年开始算起的两千二十二年。 &#xf…

Python“牵手”shopee商品评论数据采集方法,shopeeAPI申请指南

Shopee平台API接口是为开发电商类应用程序而设计的一套完整的、跨浏览器、跨平台的接口规范&#xff0c;ShopeeAPI接口是指通过编程的方式&#xff0c;让开发者能够通过HTTP协议直接访问Shopee平台的数据&#xff0c;包括商品信息、店铺信息、物流信息等&#xff0c;从而实现Sh…

如何采用敏捷方法进行需求管理?

产品的复杂性正在迅速增长&#xff0c;因此&#xff0c;为满足客户目标所需的需求数量也在扩大。这使得需求文档编制过程既耗时又可能存在风险。在本章中&#xff0c;我们将探讨如何采用敏捷方法进行需求管理。 现有的需求管理方法总是不够灵活&#xff0c;无法满足严格要求以…

薛定谔的日语学习小程序源码下载

这款学习日语的小程序源码&#xff0c;名为“薛定谔的日语”&#xff0c;首页展示了日语中的50音图&#xff0c;让用户能够看到日语词并跟读发音。 在掌握50音图后&#xff0c;用户还可以进行练习。小程序会随机提问50音图中的某一个&#xff0c;用户需要回答是否正确&#xf…

【nodejs】用Node.js实现简单的壁纸网站爬虫

1. 简介 在这个博客中&#xff0c;我们将学习如何使用Node.js编写一个简单的爬虫来从壁纸网站获取图片并将其下载到本地。我们将使用Axios和Cheerio库来处理HTTP请求和HTML解析。 2. 设置项目 首先&#xff0c;确保你已经安装了Node.js环境。然后&#xff0c;我们将创建一个…

下载安装并使用小乌龟TortoiseGit

1、下载TortoiseGit安装包 官网&#xff1a;Download – TortoiseGit – Windows Shell Interface to Githttps://tortoisegit.org/download/ 2、小乌龟汉化包 在官网的下面就有官方提供的下载包 3、安装

vue项目引入antDesignUI组件

快速安装ant-design-vue并配置&#xff0c;vue2.0 antDesign1.7.8 第一步&#xff1a;安装ant-deisgn-vue 1.7.8 npm install ant-design-vue1.7.8 --save第二步&#xff1a;配置package.json文件&#xff0c;将依赖写入后&#xff0c;npm install 安装依赖 "dependenc…

极智嘉x吉利汽车 x京东物流,引领汽车行业智慧物流新变革!

近日&#xff0c;中国领先的汽车制造商吉利汽车携手中国领先的技术驱动的供应链解决方案及物流服务商京东物流、全球仓储机器人引领者极智嘉(Geek)&#xff0c;在西安吉利汽车制造基地RDC仓库率先落地SkyPick上存下拣解决方案&#xff0c;实现了全物流链精益化、智能化、一体化…

音视频实时通话解决方案

1、问题提出 想要实现音视频通话,对于大部分人可能会觉得很难,但是实际上,有些事情并没有大家想的那样困难,只要功夫深,铁杵磨成针。 机缘巧合下,在业务中,我也遇到了一个业务场景需要实现音视频通话,我们不可能自己从零开始干,我本次用到的核心是WebRTC。 2、WebRT…

数学建模之“聚类分析”原理详解

一、聚类分析的概念 1、聚类分析&#xff08;又称群分析&#xff09;是研究样品&#xff08;或指标&#xff09;分类问题的一种多元统计法。 2、主要方法&#xff1a;系统聚类法、有序样品聚类法、动态聚类法、模糊聚类法、图论聚类法、聚类预报法等。这里主要介绍系统聚类法…

mmdetection-FasterRcnn的四层输出修改

1、 num_outs5 改为 num_outs4 2、 FPN的输出改为四层 outs [H3, H4, H5, H6] 3、strides删除一个&#xff1a;strides[4, 8, 16, 32]), 就可以了

【LeetCode】字符串匹配

字符串匹配问题 常见的字符串匹配算法包括暴力匹配、 Knuth-Morris-Pratt 算法、Boyer-Moore算法、Sunday 算法等 解题 思路 1: 两个指针各指向两个字符串的起始位置,先判断,两个字符串的第一个字符是否相同,如果相同的话,则将两个指针往下移动,并且判断较短字符串的下一个字符…

上海虚拟展厅制作平台怎么选,蛙色3DVR 助力行业发展

引言&#xff1a; 在数字化时代&#xff0c;虚拟展厅成为了企业宣传的重要手段。而作为一家位于上海的实力平台&#xff0c;上海蛙色3DVR凭借其卓越的功能和创新的技术&#xff0c;成为了企业展示和宣传的首选。 一、虚拟展厅的优势 虚拟展厅的崛起是指随着科技的进步&#x…

sass笔记

声明变量 通过$标识符进行命名及引用混合器 类似vue中的函数 通过 mixin标识定义 include 标识调用& 父选择器标识extend 进行继承可嵌套可导入 通过 import 文件位置’ 、进行导入 <style> //1 声明变量 $name: 15px; $color: skyblue;mixin border-radius($num) {/…

Linux配置Nginx SSL支持Https配置教程

Linux Nginx配置SSL 检查是否安装ssl模块安装SSL配置SSL证书 继承上篇 Linux安装Nginx 检查是否安装ssl模块 执行&#xff1a;./nginx -V命令 如果有输出--–with-http_ssl_module则说明已安装好SSL模块 安装SSL 进入安装目录/usr/local/nginx-1.22.1 执行安装命令&#xf…

软考笔记——9.软件工程

软件工程的基本原理&#xff1a;用分阶段的生命周期计划严格管理、坚持进行阶段评审、实现严格的产品控制、采用现代程序设计技术、结果应能清除的审查、开发小组的人员应少而精、承认不断改进软件工程事件的必要性。 软件工程的基本要素&#xff1a;方法、工具、过程 软件生…

【005】ts学习笔记【函数扩展】

函数扩展 参数类型 //注意&#xff0c;参数不能多传&#xff0c;也不能少传 必须按照约定的类型来 const fn (name: string , age : number ) : string > {return name age }let desc fn( "张三", 18) console.log(desc)可选参数与默认值 //可选的参数 和 默…

爬虫工作中代理失效了怎么处理?

Hey&#xff01;亲爱的爬虫小伙伴们&#xff0c;是不是经常在爬虫的工作中遇到代理IP失效的问题&#xff1f;别着急&#xff0c;今天我来分享一些应对代理失效的妙招&#xff01;这些方法简单易行&#xff0c;让你爬虫顺利进行. 一、为什么代理会失效&#xff1f; 在爬虫过程…

百度工程师浅析强化学习

作者 | Jane 导读 本文主要介绍了强化学习&#xff08;Reinforcement Learning&#xff0c;RL&#xff09;的基本概念以及什么是RL。强化学习让智能体通过与环境的交互来学习如何做出决策&#xff0c;以获得最大的累积奖励。文章还介绍了策略梯度&#xff08;Policy Gradient&a…

【golang】结构体及其方法的使用(struct)

函数是独立的程序实体。我们可以声明有名字的函数&#xff0c;也可以声明没名字的函数&#xff0c;还可以把它们当做普通的值传来传去。我们能把具有相同签名的函数抽象成独立的函数类型&#xff0c;以作为一组输入、输出&#xff08;或者说一类逻辑组件&#xff09;的代表。 …