C++ -- stack和queue

一.stack的介绍和使用

  1. stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行 元素的插入与提取操作。

  2. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。

  3. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作: empty:判空操作 back:获取尾部元素操作 push_back:尾部插入元素 pop_back:尾部删除元素操作

  4. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器, 默认情况下使用deque。

接口说明

模拟实现代码

cpp 复制代码
// 容器适配器模式
template <class T, class Container = deque<T>>
class MyStack
{
    MyStack = default;

public:
    void push(const T &x){_con.push_back(x);}

    void pop(){ _con.pop_front();}

    size_t top(){return _con.back();}

    size_t size(){return _con.size();}

    bool empty(){return _con.empty();}

private:
    Container _con;
};

可以看出与一般模拟实现不同的是,并没有手搓各种操作,而是调用增加了一个类模板来调用这个类中的已经写好的成员函数接口来实现栈的后进先出,所以表明栈只是一种模式,底层可以使用其他各种容器来实现栈的后进先出模式。

二.queue的介绍和使用

  1. 与栈一样,队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。

  2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的 成员函数来访问其元素。元素从队尾入队列,从队头出队列。

  3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操 作: empty:检测队列是否为空 size:返回队列中有效元素的个数 front:返回队头元素的引用 back:返回队尾元素的引用 push_back:在队列尾部入队列 pop_front:在队列头部出队列

  4. 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。

接口说明

默认使用标准容器deque的原因是deque支持尾插和头删,并且支持方括号查找数据,在插入操作上比vector的效率要高,并且在也可以偶尔使用方括号查找数据。

模拟实现代码

cpp 复制代码
template <class T, class Container = deque<T>>
class MyQueue
{
    MyQueue = default;

public:
    void push(const T &x){_con.push_back(x);}

    void pop(){_con.pop_front();}

    const T& back(){return _con.back();}

    const T& front(){return _con.front();}

    size_t size(){return _con.size()}

    bool empty(){return _con.empty();}
private:
    Container _con;
};

三.priority_queue的介绍和使用

  1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的(大堆)。

  2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。

  3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的"尾部"弹出,其称为优先队列的顶部。

  4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:empty():检测容器是否为空size():返回容器中有效元素个数front():返回容器中第一个元素的引用push_back():在容器尾部插入元素 pop_back():删除容器尾部元素

  5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。

  6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap和pop_heap来自动完成此操作。

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认情况下priority_queue是大堆。

适用范围:
任务调度场景:在多任务处理系统中,不同任务往往具有不同的紧急程度或重要性(即优先级),例如在操作系统中,实时性要求高的任务(如处理硬件中断)优先级高于普通的后台任务(如文件系统的定期清理),可以利用优先队列来安排任务执行顺序,确保高优先级任务先被执行。

数据排序应用:可作为一些排序算法的基础,比如堆排序。通过将待排序的数据依次插入优先队列(根据是升序还是降序需求选择最小堆或最大堆),然后依次取出元素,就能得到有序的数据序列,实现排序功能。

事件驱动模拟:在模拟现实世界中的事件顺序时,比如交通流量模拟中车辆到达路口的先后顺序、网络数据包传输中不同数据包的处理顺序等场景,每个事件都有其发生时间或重要程度作为优先级,优先队列可用于管理这些事件,决定下一个要处理的事件。

注意事项:
优先级定义的一致性:在使用优先队列时,需要明确且合理地定义元素的优先级规则,并且整个使用过程中要保持该规则的一致性。例如,如果按照数值大小定义优先级(数值小的优先级高或者数值大的优先级高),那么所有插入和比较操作都要遵循这一规则,否则可能导致元素取出顺序不符合预期。

性能考量:虽然优先队列能高效地处理基于优先级的元素操作,但不同的实现方式(如基于数组的二叉堆实现、基于链表实现等)在不同场景下的性能表现有差异。在处理大规模数据时,要根据实际情况选择合适的实现方式,例如基于二叉堆实现的优先队列在插入和删除操作时时间复杂度为 ,但如果不合理使用,频繁地调整优先级等操作可能会累积一定的性能开销。

模拟代码实现

cpp 复制代码
template <class T>
struct Less
{
    bool operator()(const T &l1, const T &l2){return l1 < l2;}
};
template <class T>
struct Greater
{
    bool operator()(const T &l1, const T &l2){return l1 > l2;}
};
template <class T, class Container = vector<T>, class Compare = Less<T>>
class Priority_queue
{
public:
    using value_type = T;
    // 向下调整
    void ajust_down(int i){
        int parent = i;
        int child = i * 2 + 1;
        while (child < size())
        {
            // 如果右子节点存在需要判断左右节点哪个更大
            if (child + 1 < size() && com(_con[child], _con[child + 1]))
                child++;

            // 大的子节点与父节点比较,父节点如果小于子节点则需要交换
            if (com(_con[parent], _con[child])){
                swap(_con[parent], _con[child]);
                parent = child;
                child  = parent * 2 + 1;
            }
            else
                break;
        }
    }
    // 向上调整
    void ajust_up(int i){
        int child = i;
        int parent = (child - 1) / 2;
        while (child > 0){
            if (com(_con[parent], _con[child])){
                swap(_con[parent], _con[child]);
                child = parent;
                parent = (child - 1) / 2;
            }
            else
                break;
        }
    }
    Priority_queue() = default;
    // 迭代器构造
    template <class Iterator>
    Priority_queue(Iterator first, Iterator last){
        while (first != last){
            _con.push_back(*first);
            first++;
        }
        // 向下建堆O(n)
        int n = size() - 1;
        // 从第一个非叶子节点开始想下建堆,保证左右子树都是堆
        for (int i = (n - 1) / 2; i >= 0; i--){ajust_down(i);}
    }

    void push(const T &x){
        _con.push_back(x);
        // 向上调整堆
        ajust_up(size() - 1);
    }

    void pop(){
        // 将头部数据移到尾部删除
        swap(_con[0], _con[size() - 1]);
        _con.pop_back();
        // 向下调整堆
        ajust_down(0);
    }

    const T &top() const{return _con[0];}

    size_t size() const{return _con.size();}

    bool empty() const{return _con.empty();}

private:
    Container _con;
    Compare com;
};

四.容器适配器

容器适配器是一种设计模式。这种设计模式将一个类的接口转换成客户希望的另外一个接口,就比如电源转换器将能够将一个二口的充电器与三口的插座接入。stack和queue就是利用了这种思想,虽然底层不是真的栈,但是可以用vector或者其他的容器实现。

相关推荐
CodeByV2 小时前
【C++】C++11:其他重要特性
开发语言·c++
2501_941111332 小时前
C++代码重构实战
开发语言·c++·算法
爱装代码的小瓶子2 小时前
【c++知识铺子】相对简单的容器适配器双生子-stack和queue(STL)
开发语言·c++
豐儀麟阁贵2 小时前
6.2 Object类
java·开发语言·python
MichaelIp2 小时前
Python同步vs异步性能对比实验-2
开发语言·python·性能优化·可用性测试
white-persist3 小时前
二进制movl及CTF逆向GDB解析:Python(env)环境下dbg从原理到实战
linux·服务器·开发语言·python·网络安全·信息可视化·系统安全
脏脏a3 小时前
类和对象(下):初始化列表、静态成员与友元深度解析
开发语言·c++
lkbhua莱克瓦243 小时前
Java进阶——集合进阶(MAP)
java·开发语言·笔记·github·学习方法·map