【C++ 学习 ⑭】- 详解 stack、queue 和 priority_queue 容器适配器

news2024/11/22 11:05:17

目录

一、详解 C++ STL 容器适配器

1.1 - 什么是容器适配器?

1.2 - 容器适配器的种类

二、详解 C++ STL deque 容器

2.1 - deque 的原理介绍

2.2 - deque 的优缺点

三、详解 stack 容器适配器

3.1 - stack 的基本介绍

3.2 - stack 的成员函数

3.3 - stack 的模拟实现

四、详解 queue 容器适配器

4.1 - queue 的基本介绍

4.2 - queue 的成员函数

4.3 - queue 的模拟实现

五、详解 priority_queue 容器适配器

5.1 - priority_queue 的基本介绍

5.2 - priority_queue 的成员函数

5.3 - priority_queue 的模拟实现


 

 


一、详解 C++ STL 容器适配器

1.1 - 什么是容器适配器?

在详解什么是容器适配器之前,初学者首先要理解适配器的含义。

其实,容器适配器中的 "适配器",和生活中常见的电源适配器中的 "适配器" 的含义非常接近。我们知道,无论是电脑、手机还是其它电器,充电时都无法直接使用 220V 的交流电,为了方便用户使用,各个电器厂商都会提供一个适用于自己产品的电源适配器,它可以将 220V 的交流电转换成适合电器使用的低压直流电。

具有多种功能的电源适配器,可以满足多种需求:

7440d4c74e8341098eca2cab820ef393.png

 

因此简单理解,容器适配器就是将不适用的序列式容器(包括 vector、deque 和 list)变得适用。容器适配器的底层即通过封装某个序列式容器,并重新组合该容器中包含的成员函数,使其满足特定场景的需要

容器适配器本质上还是容器,只不过此容器类模板的实现,利用了大量其他基础容器类模板中已经写好的成员函数。当然,如果必要的话,容器适配器中也可以自创新的成员函数

需要注意的是,STL 中的容器适配器,其内部使用的基础容器并不是固定的,用户可以在满足特定条件的多个基础容器中自由选择

 

1.2 - 容器适配器的种类

STL 提供了 3 种容器适配器,分别为 stack 栈适配器、queue 队列适配器以及 priority_queue 优先权队列适配器

容器适配器基础容器需要包含的成员函数满足条件的基础容器默认使用的基础容器
stackempty()、size()、back()、push_back()、pop_back()vector、deque、listdeque
queueempty()、size()、front()、back()、push_back()、pop_front()deque、listdeque
priority_queueempty()、size()、front()、push_back()、pop_back()vector、dequevector

因为删除 vector 容器的头部元素需要移动大量元素,效率较低,所以 vector 容器没有提供 pop_front 成员函数,因此其不能作为 queue 的基础容器

因为 list 容器不支持随机访问,所以其不能作为 priority_queue 的基础容器

由于不同的序列式容器其底层采用的数据结构不同,因此容器适配器的执行效率也不尽相同,但通常情况下,使用默认的基础容器即可。

 

 


二、详解 C++ STL deque 容器

2.1 - deque 的原理介绍

deque 是 double-ended queue 的缩写,又称双端队列容器

deque 容器和 vector 容器有很多相似的地方,例如:deque 容器也擅长在序列尾部插入或删除元素(时间复杂度为 O(1) 常数阶),而不擅长在序列中间添加或删除元素。和 vector 不同的是,deque 还擅长在序列头部插入或删除元素,时间复杂度也为 O(1) 常数阶,并且更重要的一点是,deque 并不是连续的空间,而是由一段段小空间拼接而成的,实际 deque 类似于一个动态二维数组,其底层结构如下图所示

3b64b0bcf9c24d7b8047dc13b5e859dd.png

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其 "整体连续" 以及随机访问的假象,重任就落在了 deque 的迭代器身上,因此 deque 的迭代器设计就比较复杂,如下图所示

da192373c061412db5f8b549da0c217c.png

 

那 deque 是如何借助其迭代器维护其假象连续的结构呢

d33463aca65f4b6ba1c1bd1188b94652.png

 

 

2.2 - deque 的优缺点

和 vector 相比,deque 的优点:在头部插入和删除元素时,不需要移动元素,效率较高,而且在扩容时,也不需要移动大量的元素。

和 list 相比,deque 的优点:空间利用率较高,不需要存储额外字段。

但是,deque 有一个致命缺点:不适合遍历,因为在遍历时,deque 的迭代器要频繁地去检测是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑 vector 和 list,deque 的应用场景并不多,而目前看到的一个应用就是,STL 用其作为 stack 和 queue 的默认基础容器,因为

  1. stack 和 queue 不需要遍历(因此 stack 和 queue 没有迭代器),只需要在固定的一端或两端进行操作;

  2. 在 stack 元素增长时,deque 比 vector 的效率高(扩容时不需要移动大量元素);queue 中的元素增长时,deque 不仅效率高,而且空间利用率高。

即利用了 deque 的优点,而完美地避开了其缺点。

 

 


三、详解 stack 容器适配器

3.1 - stack 的基本介绍

stack 以类模板的形式定义在 <stack> 头文件中,并位于 std 命名空间中。

template <class T, class Container = deque<T> > class stack;

stack 是一种容器适配器,专门设计用于在后进先出(LIFO,last-in first-out)的环境中使用,元素只能从容器的一端进行插入和删除。

 

526efcd1e4ae4708a6047aefe182f903.png

 

3.2 - stack 的成员函数

构造函数:

explicit stack(const container_type& ctnr = container_type());

注意:container_type 等价于 Container

构造一个 stack 容器适配器对象

empty:

bool empty() const;

size:

size_type size() const;

top:

      value_type& top();
const value_type& top() const;

push:

void push(const value_type& val);

pop:

void pop();

示例

#include <iostream>
#include <stack>
using namespace std;
​
int main()
{
    stack<int> st;
    st.push(1);
    st.push(2);
    st.push(3);
    st.push(4);
    cout << st.size() << endl;  // 4
    while (!st.empty())
    {
        cout << st.top() << " ";
        st.pop();
    }
    // 4 3 2 1
    cout << endl;
    return 0;
}

 

3.3 - stack 的模拟实现

#pragma once
​
#include <deque>
​
namespace yzz
{
    template<class T, class Container = std::deque<T>>
    class stack
    {
    public:
        bool empty() const
        {
            return _ctnr.empty();
        }
​
        size_t size() const
        {
            return _ctnr.size();
        }
​
        T& top()
        {
            return _ctnr.back();
        }
​
        const T& top() const
        {
            return _ctnr.back();
        }
​
        void push(const T& val)
        {
            _ctnr.push_back(val);
        }
​
        void pop()
        {
            _ctnr.pop_back();
        }
    private:
        Container _ctnr;
    };
}

 

 


四、详解 queue 容器适配器

4.1 - queue 的基本介绍

queue 以类模板的形式定义在 <queue> 头文件中,并位于 std 命名空间中。

template < class T, class Container = deque<T> > class queue;

queue 是一种容器适配器,专门设计用于在先进先出(FIFO,first-in first-out)的环境中使用,元素只能从容器的一端插入,并从另一端删除。

1a98d7c9fbfe4fb8b8aff847b8871a41.png

 

 

4.2 - queue 的成员函数

构造函数:

explicit queue(const container_type& ctnr = container_type());

empty:

bool empty() const;

size:

size_type size() const;

front:

      value_type& front();
const value_type& front() const;

back:

      value_type& back();
const value_type& back() const;

push:

void push(const value_type& val);

pop:

void pop();

示例

#include <iostreamm>
#include <queue>
using namespace std;
​
int main()
{
    queue<int> q;
    q.push(1);
    q.push(2);
    q.push(3);
    q.push(4);
    cout << q.size() << endl;  // 4
    cout << q.front() << " " << q.back() << endl;  // 1 4
    while (!q.empty())
    {
        cout << q.front() << " ";
        q.pop();
    }
    // 1 2 3 4
    cout << endl;
    return 0;
}

 

4.3 - queue 的模拟实现

#pragma once
​
#include <deque>
​
namespace yzz
{
    template<class T, class Container = std::deque<T>>
    class queue
    {
    public:
        bool empty() const
        {
            return _ctnr.empty();
        }
​
        size_t size() const
        {
            return _ctnr.size();
        }
​
        T& front()
        {
            return _ctnr.front();
        }
​
        const T& front() const
        {
            return _ctnr.front();
        }
​
        T& back()
        {
            return _ctnr.back();
        }
​
        const T& back() const
        {
            return _ctnr.back();
        }
​
        void push(const T& val)
        {
            _ctnr.push_back(val);
        }
​
        void pop()
        {
            _ctnr.pop_front();
        }
    private:
        Container _ctnr;
    };
}

 

 


五、详解 priority_queue 容器适配器

5.1 - priority_queue 的基本介绍

priority_queue 以类模板的形式定义在<queue> 头文件中,并位于 std 命名空间中。

template < class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue; 

priority_queue 是一种容器适配器,其模拟的也是队列这种存储结构,即使用此容器适配器存储元素,只能从一进(称为队尾),从另一端出(称为队头),且每次只能访问 priority_queue 中位于队头的元素。

但是,priority_queue 容器适配器中元素的存和取,遵循的并不是 "first-in first-out"(先进先出)原则,而是 "first-in largest-out" 原则,即先进队列的元素不一定先出队列,而是优先级最大的元素先出队列

priority_queuue 容器适配器的 "first-in largest-out" 特性,和它底层采用堆(heap)结构存储数据是分不开的,注意:priority_queue 默认情况下是大根堆

 

5.2 - priority_queue 的成员函数

构造函数:

initialize (1) explicit priority_queue(const Compare& Cmp = Compare(), 
                                       const Container& ctnr = Container());
     range (2) template <class InputIterator>
               priority_queue(InputIterator first, InputIterator last,
                              const Compare& Cmp = Compare(), 
                              const Container& ctnr = Container()));

empty:

bool empty() const;

size:

size_type size() const;

top:

const value_type& top() const;

push:

void push(const value_type& val);

pop:

void pop();

示例一

#include <iostream>
#include <queue>
using namespace std;
​
int main()
{
    priority_queue<int> pq;
    pq.push(30);
    pq.push(100);
    pq.push(25);
    pq.push(40);
    cout << pq.size() << endl;  // 4
    while (!pq.empty())
    {
        cout << pq.top() << " ";
        pq.pop();
    }
    // 100 40 30 25
    cout << endl;
    return 0;
}

示例二 - 如果 priority_queue 中存储的是自定义类型的数据,用户则需要在自定义类型中提供 < 和 > 的重载

#include <iostream>
#include <queue>
using namespace std;
​
class Date
{
    friend ostream& operator<<(ostream& out, const Date& d);
public:
    Date(int year = 1949, int month = 10, int day = 1)
        : _year(year), _month(month), _day(day)
    { }
​
    bool operator<(const Date& d) const
    {
        return (_year < d._year)
            || (_year == d._year && _month < d._month)
            || (_year == d._year && _month == d._month && _day < d._day);
    }
​
    bool operator>(const Date& d) const
    {
        return (_year > d._year)
            || (_year == d._year && _month > d._month)
            || (_year == d._year && _month == d._month && _day > d._day);
    }
private:
    int _year;
    int _month;
    int _day;
};
​
ostream& operator<<(ostream& out, const Date& d)
{
    return out << d._year << "-" << d._month << "-" << d._day;
}
​
int main()
{
    priority_queue<Date, vector<Date>, greater<Date>> pq;
    pq.push(Date(2023, 5, 1));
    pq.push(Date(2023, 7, 1));
    pq.push(Date(2023, 6, 1));
    pq.push(Date(2023, 4, 1));
    cout << pq.size() << endl;  // 4
    while (!pq.empty())
    {
        cout << pq.top() << " ";
        pq.pop();
    }
    // 2023-4-1 2023-5-1 2023-6-1 2023-7-1
    cout << endl;
    return 0;
}

 

5.3 - priority_queue 的模拟实现

#pragma once
​
#include <vector>
#include <assert.h>
​
namespace yzz
{
    // 函数对象(仿函数)类
    template<class T>
    struct less
    {
        bool operator()(const T& left, const T& right)
        {
            return left < right;
        }
    };
​
    template<class T>
    struct greater
    {
        bool operator()(const T& left, const T& right)
        {
            return left > right;
        }
    };
​
​
    // priority_queue 容器适配器
    template<class T, class Container = std::vector<T>, class Compare = less<T>>
    class priority_queue
    {
    private:
        void AdjustUp(int child)
        {
            Compare cmp;
            int parent = (child - 1) / 2;
            while (child > 0)
            {
                if (cmp(_ctnr[parent] ,_ctnr[child]))
                {
                    std::swap(_ctnr[parent], _ctnr[child]);
                    child = parent;
                    parent = (child - 1) / 2;
                }
                else
                {
                    break;
                }
            }
        }
​
        void AdjustDown(int parent)
        {
            Compare cmp;
            int sz = _ctnr.size();
            int child = 2 * parent + 1;  // 双亲节点默认和其左孩子进行比较
            while (child < sz)
            {
                if (child + 1 < sz && cmp(_ctnr[child] ,_ctnr[child + 1]))
                {
                    ++child;  // 改为让双亲节点和其右孩子进行比较
                }
​
                if (cmp(_ctnr[parent] ,_ctnr[child]))
                {
                    std::swap(_ctnr[parent], _ctnr[child]);
                    parent = child;
                    child = 2 * parent + 1;
                }
                else
                {
                    break;
                }
            }
        }
​
    public:
        priority_queue() 
        { }
​
        template<class InputIterator>
        priority_queue(InputIterator first, InputIterator last)
        {
            while (first != last)
            {
                push(*first);
                ++first;
            }
        }
​
        bool empty() const
        {
            return _ctnr.empty();
        }
​
        size_t size() const
        {
            return _ctnr.size();
        }
​
        const T& top() const
        {
            return _ctnr.front();
        }
​
        void push(const T& val)
        {
            _ctnr.push_back(val);
            AdjustUp(_ctnr.size() - 1);
        }
​
        void pop()
        {
            assert(!_ctnr.empty()); // 前提是 _ctnr 非空
            std::swap(_ctnr[0], _ctnr[_ctnr.size() - 1]);
            _ctnr.pop_back();
            AdjustDown(0);
        }
    private:
        Container _ctnr;
    };
}

 

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

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

相关文章

live555在Windows WSL2中编译、运行,搭建RTSP流服务器

文章目录 1. 背景2. 实施步骤2.1 下载live555安装包2.2 解压压缩包2.3 编译源码2.3 安装ffmpeg2.4 安装opencv-python2.5 视频文件格式转换2.6 启动推流2.6 安装VLC&#xff0c;验证 3. 用opencv-python接口接收视频流参考 1. 背景 想要通过RTSP往opencv的接口中推流&#xff…

富文本中的参数如何设置大小?

富文本里如果直接显示的数据可以随意调节大小,颜色等格式. 但是如果数据是动态的参数 就无法随意设置参数的格式了.当字数过多时就会出现显示不全,有没有大佬知道如何解决? wangEditor set HTML

Flink 数据集成服务在小红书的降本增效实践

摘要&#xff1a;本文整理自实时引擎研发工程师袁奎&#xff0c;在 Flink Forward Asia 2022 数据集成专场的分享。本篇内容主要分为四个部分&#xff1a; 小红书实时服务降本增效背景Flink 与在离线混部实践实践过程中遇到的问题及解决方案未来展望 点击查看原文视频 & 演…

【学习笔记之vue】These dependencies were not found:

These dependencies were not found:方案一 全部安装一遍 我们先浅试一个axios >> npm install axios 安装完报错就没有axios了&#xff0c;验证咱们的想法没有问题&#xff0c;实行&#xff01; ok

chapter 3 Free electrons in solid - 3.2 量子自由电子理论对一些现象的解释

3.2 自由电子气的热容 Heat capacity of free electron gas 3.2.1 计算自由电子的热容 Calculation of Heat Capacity of free Electrons T>0K, total energy of free electrons: E ∫ E d N 3 5 N e E F 0 [ 1 5 12 π 2 ( k B T E F 0 ) 2 ] E \int EdN \frac{3}{5}…

Unable to find resource t64.exe in package pip._vendor.distlib报错问题解决

Unable to find resource t64.exe in package pip._vendor.distlib报错问题解决 问题报错具体内容具体解决方案解决方法一解决方法二 问题报错具体内容 想要对python的版本进行一个升级,使用如下语句 python -m pip install --upgrade pip出现如下报错 Unable to find reso…

四维轻云场景更新之后,如何快速搭建场景?

「四维轻云」是一款轻量化的地理空间数据管理云平台&#xff0c;支持地理空间数据的在线管理、编辑以及分享。平台提供了项目管理、数据上传、场景搭建、发布分享、素材库等功能模块&#xff0c;支持团队多用户在线协作管理。 四维轻云平台是以项目的形式进行数据管理&#xf…

vue2中常用的组件封装技巧

文章目录 vue组件封装技巧1.props参数2.$attrs3.$emit4.$listeners5.插槽 vue组件封装技巧 1.props参数 最简单父传子的技巧&#xff0c;在父组件传入值&#xff0c;子组件声明props接收就可以了 如下&#xff1a; 父组件&#xff1a; <template><div id"ap…

两图看懂什么叫回调函数,使用回调函数实现多态

#include <stdio.h> //任务函数可以增加&#xff0c;任务使用框架函数保持不变&#xff0c;根据传入函数指针的不同调用不同的任务函数&#xff0c;从而实现了多态 //编写任务函数&#xff0c;任务函数可以增加 int Myadd1(int a, int b) { return ab; } int Mymux2(i…

Git 设置代理

Git 传输分两种协议&#xff0c;SSH和 http(s)&#xff0c;设置代理也需要分两种。 http(s) 代理 Command Line 使用 命令行 模式&#xff0c;可以在Powershell中使用以下命令设置代理&#xff1a; $env:http_proxy"http://127.0.0.1:7890" $env:https_proxy&quo…

【LeetCode】买卖股票的最佳时机最多两次购买机会

买卖股票的最佳时机 题目描述算法分析程序代码 链接: 买卖股票的最佳时机 题目描述 算法分析 程序代码 class Solution { public:int maxProfit(vector<int>& prices) {int n prices.size();vector<vector<int>> f(n,vector<int>(3,-0x3f3f3f))…

【从零学习python 】48.Python中的继承与多继承详解

文章目录 在Python中&#xff0c;继承可以分为单继承、多继承和多层继承。单继承 继承语法多继承 语法格式使用多继承时需要注意以下事项Python中的MRO新式类和旧式&#xff08;经典&#xff09;类 进阶案例 在Python中&#xff0c;继承可以分为单继承、多继承和多层继承。 单…

.NET把文件嵌入到程序集中的EmbeddedFile

简介 有时候我们在发布程序时&#xff0c;不想让客户看到项目中的文件&#xff0c;这时就可以使用.NET的嵌入文件功能&#xff08;虚拟文件&#xff09;。在.NET中&#xff0c;虚拟文件&#xff08;Virtual File&#xff09;是一种特殊类型的文件&#xff0c;它们在编译时被嵌…

手把手教你用Git备份保存代码到云端

第一步 &#xff1a;创建一个Gitee仓库,需要先下载安装git,在下载安装完成后&#xff0c;我们需要首先去Gittee官网上创建一个仓库 路径会自动生成&#xff0c;点击创建即可 第二步&#xff1a; 方法一&#xff1a;用图形化界面&#xff08;方便&#xff09; 下载&#xff…

听说换上人大金仓“自动挡”,效率提升4000多倍!

前情提要 随着数据规模以及应用业务场景复杂度的不断增加&#xff0c;更大规模数据量的数据库应用场景出现&#xff0c;对于传统关系型数据库的优化能力要求也更高。 因为SQL是解释性语言&#xff0c;用户可以指定要什么&#xff0c;而无需关心如何达成目的。所以对于同样一条S…

录取结果怎么公布?这个技术可以轻松实现

对于录取结果的公布&#xff0c;一直是考生和家长重点关注的话题&#xff0c;因此学校公布录取情况的方式和效果也一直是学校工作的重点和难点&#xff0c;传统的公布方式涉及技术开发和服务器搭建&#xff0c;开发周期也较长&#xff01;一般有经验的老教师都会选择使用易查分…

分布式 - 消息队列Kafka:Kafka 消费者消费位移的提交方式

文章目录 1. 自动提交消费位移2. 自动提交消费位移存在的问题&#xff1f;3. 手动提交消费位移1. 同步提交消费位移2. 异步提交消费位移3. 同步和异步组合提交消费位移4. 提交特定的消费位移5. 按分区提交消费位移 4. 消费者查找不到消费位移时怎么办&#xff1f;5. 如何从特定…

JavaSE-17 【异常】

第一章 什么是异常 1.1 异常的概念 异常&#xff1a;指的是程序在执行过程中&#xff0c;出现的非正常的情况&#xff0c;最终会导致JVM的非正常停止 在Java中&#xff0c;异常本身就是一个类&#xff0c;产生异常就是创建一个异常对象并且抛出一个异常对象的过程 Java处理…

如何快速高效地进行 API 自动化测试

我们的研发团队最需要应对的就是各种新需求。软件越来越快的更新速度也让整个系统也变得越来越复杂&#xff0c;这让 测试 工作面临着巨大的挑战。测试人员必须与开发人员沟通&#xff0c;确定测试范围&#xff0c;并及时获取最新的接口用例数据来验证功能。但是&#xff0c;由…

【Apollo】Apollo版本特点与改进

特点与改进 概述里程碑6.0版本特点及改进7.0版本特点及改进8.0版本特点及改进代码差异 主页传送门&#xff1a;&#x1f4c0; 传送 概述 Apollo (阿波罗)是一个开放的、完整的、安全的平台&#xff0c;将帮助汽车行业及自动驾驶领域的合作伙伴结合车辆和硬件系统&#xff0c;快…