C++ :STL中deque的原理

deque的结构类似于哈希表,使用一个指针数组存储固定大小的数组首地址,当数据分布不均匀时将指针数组内的数据进行偏移,桶不够用的时候会像vector一样扩容然后将之前数组中存储的指针拷贝过来,从原理可以看出deque的性能是非常高的,它不存咋像vector那样大规模的数据拷贝和大批量连续空间的需求同时也弥补了像list不支持随机访问的缺点,可以说deque集中了list和vector的大部分优点,当然缺点很致命在中间节点插入数据的时候会异常复杂,高效的前后端插入机制使得stack和queue都适配于deque。

vector动态数组

  • 支持随机访问
  • 可以自动扩容
  • 只支持末端插入

list双向链表

  • 不支持随机访问
  • 支持任意位置插入
  • 无需扩容

deque双端队列(vector< Array >)

  • 支持随机访问(伪随机)
  • 微量扩容
  • 支持前后端插入

看下面一段代码

#include<iostream>
#include<deque>
using namespace std;
struct T{~T(){cout<<"T析构";}}
void Add_head(deque<int>&q){
    q.push_front(1);
    cout<<&q.front()<<" "<<endl;
}

void Add_tail(deque<int>&q){
    q.push_back(1);
    cout<<&q.back()<<" "<<endl;
}

int main(){
    deque<int>q;
    for(int i=0;i<10;i++) Add_head(q);
    for(int i=0;i<10;i++) Add_tail(q);
}

在这里插入图片描述
从地址大概可以看出这个东西是一块一块的,块的大小是固定的,而且块内地址是连续的,想要获取更多的信息的话还要结合源码,网上有说deque是数组链表的,但如果简单的理解为链表的话就大错特错了,这么理解的话随机访问是实现不了的,我觉得理解为一种特殊的二维数组比较好。

下面看具体一些功能的实现

看下_Deque_val模板,_Block_size 表示单个数组元素个数,这里是根据元素的大小决定单个数组的大小,_Mapptr 是桶里面装一维数组的指针,_Mapsize桶的大小根据注释可以看到桶的扩容是指数级的,_Myoff存储偏移量,_Mysize存储元素个数
注意:这个偏移量是针对_Mapptr 的头部开始到当前元素的偏移量,此外单个数组的大小受限于数据大小,所以很多时候双端队列会表现的像维护在指针数组上的链表

// CLASS TEMPLATE _Deque_val
template <class _Val_types>
class _Deque_val : public _Container_base12 {
public:
    using value_type      = typename _Val_types::value_type;
    using size_type       = typename _Val_types::size_type;
    using difference_type = typename _Val_types::difference_type;
    using pointer         = typename _Val_types::pointer;
    using const_pointer   = typename _Val_types::const_pointer;
    using reference       = value_type&;
    using const_reference = const value_type&;
    using _Mapptr         = typename _Val_types::_Mapptr;
private:
    static constexpr size_t _Bytes = sizeof(value_type);
public:
    static constexpr int _Block_size =
        _Bytes <= 1 ? 16 : _Bytes <= 2 ? 8 : _Bytes <= 4 ? 4 : _Bytes <= 8 ? 2 : 1; // elements per block (a power of 2)
    _Deque_val() noexcept : _Map(), _Mapsize(0), _Myoff(0), _Mysize(0) {}
    size_type _Getblock(size_type _Off) const noexcept {
        // NB: _Mapsize and _Block_size are guaranteed to be powers of 2
        return (_Off / _Block_size) & (_Mapsize - 1);
    }
    _Mapptr _Map; // pointer to array of pointers to blocks
    size_type _Mapsize; // size of map array, zero or 2^N
    size_type _Myoff; // offset of initial element
    size_type _Mysize; // current length of sequence
};

这个是_Mapptr 这个桶的基类,可以看到使用的是二级指针,它的作用就是维护一种二维的结构,这时候可能好奇了它作为一个单独的模板是如何适配作为_Deque_val 模板类型的一部分的。

template <class _Ty>
struct _Deque_simple_types : _Simple_types<_Ty> {
    using _Mapptr = _Ty**;
};

这个适配器完成了适配工作,将多个类适配为一个类很大程度上提高了代码的可读性,使得代码可以更高的遵循开闭原则。

template <bool _Test, class _Ty1, class _Ty2>
struct conditional { // Choose _Ty1 if _Test is true, and _Ty2 otherwise
    using type = _Ty1;
};
template <class _Ty1, class _Ty2>
struct conditional<false, _Ty1, _Ty2> {
    using type = _Ty2;
};
template <bool _Test, class _Ty1, class _Ty2>
using conditional_t = typename conditional<_Test, _Ty1, _Ty2>::type;

deque中迭代器访问元素,使用偏移量获取存储的行和列。

_NODISCARD reference operator*() const noexcept {
        const auto _Mycont = static_cast<const _Mydeque*>(this->_Getcont());
#if _ITERATOR_DEBUG_LEVEL != 0
        _STL_VERIFY(_Mycont, "cannot dereference value-initialized deque iterator");
        _STL_VERIFY(_Mycont->_Myoff <= this->_Myoff && this->_Myoff < _Mycont->_Myoff + _Mycont->_Mysize,
            "cannot deference out of range deque iterator");
#endif // _ITERATOR_DEBUG_LEVEL != 0

        _Size_type _Block = _Mycont->_Getblock(_Myoff);
        _Size_type _Off   = _Myoff % _Block_size;
        return _Mycont->_Map[_Block][_Off];
    }

那么现在就有一个问题,如果插入元素时集中在头部或者尾部时它会直接扩容吗?带着问题接着分析。

首先看一下emplace_back函数

template <class... _Tys>
    void _Emplace_back_internal(_Tys&&... _Vals) {
        if ((_Myoff() + _Mysize()) % _Block_size == 0 && _Mapsize() <= (_Mysize() + _Block_size) / _Block_size) {
            _Growmap(1);
        }
        _Myoff() &= _Mapsize() * _Block_size - 1;
        size_type _Newoff = _Myoff() + _Mysize();
        size_type _Block  = _Getblock(_Newoff);
        if (_Map()[_Block] == nullptr) {
            _Map()[_Block] = _Getal().allocate(_Block_size);
        }
        _Alty_traits::construct(
            _Getal(), _Unfancy(_Map()[_Block] + _Newoff % _Block_size), _STD forward<_Tys>(_Vals)...);
        ++_Mysize();
    }

可以看到整体逻辑和vector差不多,emplace_front也差不多就不看了,因为站在函数角度它的不需要关注是头插还是尾插。
这里的策略依旧是可以插入时插入不能插入时进行处理,直接看扩容处理函数,和直接注释的一样,如果没有可插入的空间时桶的大小变为原来的二倍,然后将指针挪到新的数组的中间即可,偏移量这些重新计算,这个环节虽然看起来很麻烦但是注意这里是针对指针的而不是针对元素的,所以说效率还是蛮高的。

void _Growmap(size_type _Count) { // grow map by at least _Count pointers, _Mapsize() a power of 2
        static_assert(1 < _Minimum_map_size, "The _Xlen() test should always be performed.");
        _Alpty _Almap(_Getal());
        size_type _Newsize = 0 < _Mapsize() ? _Mapsize() : 1;
        while (_Newsize - _Mapsize() < _Count || _Newsize < _Minimum_map_size) {
            // scale _Newsize to 2^N >= _Mapsize() + _Count
            if (max_size() / _Block_size - _Newsize < _Newsize) {
                _Xlen(); // result too long
            }
            _Newsize *= 2;
        }
        _Count = _Newsize - _Mapsize();
        size_type _Myboff = _Myoff() / _Block_size;
        _Mapptr _Newmap   = _Almap.allocate(_Mapsize() + _Count);
        _Mapptr _Myptr    = _Newmap + _Myboff;
        _Myptr = _STD uninitialized_copy(_Map() + _Myboff, _Map() + _Mapsize(), _Myptr); // copy initial to end
        if (_Myboff <= _Count) { // increment greater than offset of initial block
            _Myptr = _STD uninitialized_copy(_Map(), _Map() + _Myboff, _Myptr); // copy rest of old
            _Uninitialized_value_construct_n_unchecked1(_Myptr, _Count - _Myboff); // clear suffix of new
            _Uninitialized_value_construct_n_unchecked1(_Newmap, _Myboff); // clear prefix of new
        } else { // increment not greater than offset of initial block
            _STD uninitialized_copy(_Map(), _Map() + _Count, _Myptr); // copy more old
            _Myptr = _STD uninitialized_copy(_Map() + _Count, _Map() + _Myboff, _Newmap); // copy rest of old
            _Uninitialized_value_construct_n_unchecked1(_Myptr, _Count); // clear rest to initial block
        }
        _Destroy_range(_Map() + _Myboff, _Map() + _Mapsize());
        if (_Map() != _Mapptr()) {
            _Almap.deallocate(_Map(), _Mapsize()); // free storage for old
        }
        _Map() = _Newmap; // point at new
        _Mapsize() += _Count;
    }

stack和queue其实都是deque的适配器类,下面举个例子

template<class T,class dq=deque<T>>
class stk{
protected:
    dq d;
public:
    void pop(){
        d.pop_back();
    }
    void push(const T& val){
        d.push_back(val);
    }
    const T& top()const{
        return d.back();
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年腾讯云4核8G服务器性能怎么样?价格有点便宜

腾讯云4核8G服务器价格&#xff1a;轻量4核8G12M优惠价格646元15个月、CVM S5服务器4核8G配置1437元买1年送3个月。腾讯云4核8G服务器支持多少人同时在线&#xff1f;支持30个并发数&#xff0c;可容纳日均1万IP人数访问。腾讯云百科txybk.com整理4核8G服务器支持多少人同时在线…

话题通信的python实现

一、发布者Publisher的python实现 step1&#xff1a;在scripts文件夹中创建py节点 step2&#xff1a;第一行是为了指定解释器&#xff0c;Ubuntu20.04是python3&#xff0c;比他低的版本是python。第二行是为了指定编码方式。第五行中&#xff0c;引用index.ros.org中数据类型…

E5063A是德科技E5063A网络分析仪

181/2461/8938产品概述&#xff1a; Keysight E5063A 是一款低成本网络分析仪&#xff0c;可为测试天线、电缆、滤波器和 PCB 等简单无源元件提供优化的性能和功能。Keysight E5063A 为您的企业提供价格和性能之间的最佳平衡&#xff0c;以满足您的业务和技术要求。它利用行业…

R60ABD1 呼吸心跳雷达睡眠监测模块

R60ABD1 呼吸心跳雷达睡眠监测模块 简介特征参数电气参数通讯协议说明使用步骤总结 简介 R60ABD1 雷达模块基于一发三收天线形式&#xff1a;宽波束雷达模块主要适用于置顶安装模式&#xff0c;通过算法控制一定角度范围&#xff0c;精准扫描人体全身的动作层析&#xff1b;实…

Kubernetes篇(一)— kubernetes介绍

目录 前言一、应用部署方式演变二、kubernetes简介三、kubernetes组件四、kubernetes概念 前言 本章节主要介绍应用程序在服务器上部署方式演变以及kubernetes的概念、组件和工作原理。 一、应用部署方式演变 在部署应用程序的方式上&#xff0c;主要经历了三个时代&#xff…

HTLM 之 vscode 插件推荐

文章目录 vscode 插件live Serverprettiersetting 保存这个文档的更改Material Theme / Material Theme icon vscode 插件 live Server prettier setting 搜索 format default 保存这个文档的更改 cmds // mac ctrls // win Material Theme / Material Theme icon 来更换…

使用Flink实现Kafka到MySQL的数据流转换:一个基于Flink的实践指南

使用Flink实现Kafka到MySQL的数据流转换 在现代数据处理架构中&#xff0c;Kafka和MySQL是两种非常流行的技术。Kafka作为一个高吞吐量的分布式消息系统&#xff0c;常用于构建实时数据流管道。而MySQL则是广泛使用的关系型数据库&#xff0c;适用于存储和查询数据。在某些场景…

算法学习——LeetCode力扣动态规划篇3

算法学习——LeetCode力扣动态规划篇3 494. 目标和 494. 目标和 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 ‘’ 或 ‘-’ &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 …

用JSch实现远程传输文件并打包成jar

本文将简单介绍一下 JSch 这个Java的第三方库的一个简单用法&#xff0c;并以此为实例&#xff0c;讲解 IntelliJ 中打包成 jar 包的2种方式。 实现目标 我们的目标是&#xff0c;做出一个jar包&#xff0c;它能够实现类似于 scp 命令的远程传输文件的功能。用法如下&#xf…

成为嵌入式编程高手:C语言学习网站大揭秘!

介绍&#xff1a;嵌入式C语言是针对嵌入式系统开发的一种编程语言&#xff0c;它基于标准的C语言&#xff0c;但进行了特定的优化和调整&#xff0c;以适应嵌入式环境的特殊需求。以下是对嵌入式C语言的详细介绍&#xff1a; 语法基础&#xff1a;嵌入式C语言在语法上与标准C语…

支付后打开半屏小程序能力的相关调整通知

来源&#xff1a;小程序官方公告 各位开发者&#xff1a; 打开半屏小程序 能力是微信团队提供的一项方便用户从小程序便捷打开另一个小程序的轻量化体验能力。为了优化用户体验&#xff0c;避免用户在没有预期的情况下以半屏方式打开另一个小程序&#xff0c;微信团队将回收支…

代码学习记录31---动态规划开始

随想录日记part31 t i m e &#xff1a; time&#xff1a; time&#xff1a; 2024.03.29 主要内容&#xff1a;今天开始要学习动态规划的相关知识了&#xff0c;今天的内容主要涉及四个方面&#xff1a; 理论基础 ; 斐波那契数 ;爬楼梯 ;使用最小花费爬楼梯。 理论基础 509. 斐…

平价运动型蓝牙耳机哪个牌子好?精心筛选五大必购产品分享!

蓝牙耳机已成为现代人生活中不可或缺的一部分&#xff0c;特别是那些追求健康、热爱运动的朋友们&#xff0c;平价且实用的运动型蓝牙耳机更是他们的首选&#xff0c;在众多的品牌与型号中&#xff0c;如何选择一款既符合预算又满足运动需求的蓝牙耳机呢&#xff1f;今天就让我…

个人优势能力测评,寻找你的天赋

个人优势能力测评&#xff0c;用来发现自己的天赋&#xff0c;也被称之为多元智力测评&#xff0c;该理论认为人的智力不仅仅是逻辑思维能力&#xff0c;每个人的天赋不同&#xff0c;具有多样性&#xff0c;目前的智力测试基本上都以逻辑思维&#xff0c;推理能力为主&#xf…

C++项目——集群聊天服务器项目(九)客户端异常退出业务

服务器端应检测到客户端是否异常退出&#xff0c;因此本节来实现客户端异常退出&#xff0c;项目流程见后文 一、客户端异常退出业务流程 &#xff08;1&#xff09;在业务模块定义处理客户端异常退出的函数 &#xff08;2&#xff09;集群聊天服务器项目(八&#xff09;提到…

剑指Offer题目笔记23(归并排序)

面试题77&#xff1a; 问题&#xff1a; ​ 输入一个链表的头节点&#xff0c;将该链表排序。 解决方案&#xff1a; ​ 使用归并排序。将链表分为两个子链表&#xff0c;在对两个子链表排序后再将它们合并为一个排序的链表。 源代码&#xff1a; /*** Definition for sin…

Python循环语句for

主要解决什么样的问题&#xff1a;具有重复性、规律性的问题 循环四要素&#xff1a; 循环的开始&#xff08;从第1步开始&#xff1b;从第1步开始/从起点开始&#xff09; 循环的继续条件&#xff08;还没走到第10步&#xff1b;没有碰到墙/就是看距离&#xff09; 循环体&…

算法学习——LeetCode力扣动态规划篇4

算法学习——LeetCode力扣动态规划篇4 377. 组合总和 Ⅳ 377. 组合总和 Ⅳ - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个由 不同 整数组成的数组 nums &#xff0c;和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。 题目数据保…

JSQLParserException异常

前言 SQL中加入了租户字段&#xff0c;报这个错&#xff0c;可以查出数据&#xff0c;但是不多&#xff1b;SQL检查无问题 解决 原因一 引入新的SQL解析器检查解析SQL&#xff0c;与mybatis多租户无关 参考 <!--jsqlparser版本太低也无法解析&#xff0c;如2.0--> &…

左侧或水平导航菜单栏与main区域联动

系列文章目录 一、elementui 导航菜单栏和Breadcrumb 面包屑关联 二、左侧导航菜单栏与main区域联动 文章目录 系列文章目录前言一、实现步骤1.<el-menu>中设置属性router为true2.<el-menu-item>中设置路由 route"/"3.<el-main>里设置路由出口4…