C++ STL容器适配器深度解析:stack、queue与priority_queue

目录

[📚 一、容器适配器概述](#📚 一、容器适配器概述)

[1.1 什么是容器适配器?](#1.1 什么是容器适配器?)

[1.2 核心特点](#1.2 核心特点)

[🗃️ 二、stack(栈)](#🗃️ 二、stack(栈))

[2.1 栈的基本概念](#2.1 栈的基本概念)

[2.2 栈的接口](#2.2 栈的接口)

[2.3 栈的经典应用](#2.3 栈的经典应用)

[2.3.1 最小栈(MinStack)](#2.3.1 最小栈(MinStack))

[2.3.2 栈的弹出/压入序列](#2.3.2 栈的弹出/压入序列)

[2.3.3 逆波兰表达式求值](#2.3.3 逆波兰表达式求值)

[2.4 stack的模拟实现](#2.4 stack的模拟实现)

[🚶 三、queue(队列)](#🚶 三、queue(队列))

[3.1 队列的基本概念](#3.1 队列的基本概念)

[3.2 队列的接口](#3.2 队列的接口)

[3.3 经典应用:用队列实现栈](#3.3 经典应用:用队列实现栈)

[3.4 queue的模拟实现](#3.4 queue的模拟实现)

[🏆 四、priority_queue(优先级队列)](#🏆 四、priority_queue(优先级队列))

[4.1 优先级队列的基本概念](#4.1 优先级队列的基本概念)

[4.2 priority_queue的接口](#4.2 priority_queue的接口)

[4.3 使用示例](#4.3 使用示例)

[4.4 自定义类型在priority_queue中的使用](#4.4 自定义类型在priority_queue中的使用)

[4.5 经典应用:数组中第K个最大元素](#4.5 经典应用:数组中第K个最大元素)

[4.6 priority_queue的模拟实现(简化版)](#4.6 priority_queue的模拟实现(简化版))

[🔄 五、底层容器与deque](#🔄 五、底层容器与deque)

[5.1 为什么选择deque作为默认底层容器?](#5.1 为什么选择deque作为默认底层容器?)

[5.2 deque的结构特点](#5.2 deque的结构特点)

[5.3 不同底层容器的选择](#5.3 不同底层容器的选择)

[🎯 六、选择指南与最佳实践](#🎯 六、选择指南与最佳实践)

[6.1 何时使用stack?](#6.1 何时使用stack?)

[6.2 何时使用queue?](#6.2 何时使用queue?)

[6.3 何时使用priority_queue?](#6.3 何时使用priority_queue?)

[6.4 性能对比](#6.4 性能对比)

[📝 七、总结](#📝 七、总结)


📚 一、容器适配器概述

1.1 什么是容器适配器?

容器适配器是一种设计模式,它将一个类的接口转换成用户希望的另一个接口。在C++ STL中,stack、queue和priority_queue都属于容器适配器,它们基于现有的容器(如vector、list、deque)提供特定功能。

1.2 核心特点

  • 不提供完整的容器功能,只提供特定操作

  • 基于现有容器实现,复用底层数据结构

  • 隐藏底层实现细节,提供简洁的接口

🗃️ 二、stack(栈)

2.1 栈的基本概念

栈是一种**后进先出(LIFO)**的数据结构,只允许在栈顶进行插入和删除操作。

2.2 栈的接口

函数声明 接口说明
stack() 构造空的栈
empty() 检测栈是否为空
size() 返回栈中元素的个数
top() 返回栈顶元素的引用
push(const T& val) 将元素val压入栈中
pop() 将栈顶元素弹出

2.3 栈的经典应用

2.3.1 最小栈(MinStack)

cpp

复制代码
class MinStack {
public:
    void push(int x) {
        // 保存所有元素
        _elem.push(x);
        
        // 如果x小于等于_min栈顶元素,压入_min栈
        if(_min.empty() || x <= _min.top())
            _min.push(x);
    }
    
    void pop() {
        // 如果出栈元素等于_min栈顶元素,_min也出栈
        if(_min.top() == _elem.top())
            _min.pop();
        _elem.pop();
    }
    
    int top() { return _elem.top(); }
    int getMin() { return _min.top(); }
    
private:
    stack<int> _elem;  // 保存所有元素
    stack<int> _min;   // 保存最小值
};
2.3.2 栈的弹出/压入序列

cpp

复制代码
bool IsPopOrder(vector<int> pushV, vector<int> popV) {
    if(pushV.size() != popV.size()) return false;
    
    stack<int> s;
    int pushIdx = 0;
    
    for(int popIdx = 0; popIdx < popV.size(); popIdx++) {
        // 当栈为空或栈顶不等于弹出元素时,压入元素
        while(s.empty() || s.top() != popV[popIdx]) {
            if(pushIdx < pushV.size())
                s.push(pushV[pushIdx++]);
            else
                return false;
        }
        s.pop();  // 栈顶等于弹出元素,出栈
    }
    return true;
}
2.3.3 逆波兰表达式求值

cpp

复制代码
int evalRPN(vector<string>& tokens) {
    stack<int> s;
    
    for(const auto& token : tokens) {
        if(token == "+" || token == "-" || token == "*" || token == "/") {
            int right = s.top(); s.pop();
            int left = s.top(); s.pop();
            
            if(token == "+") s.push(left + right);
            else if(token == "-") s.push(left - right);
            else if(token == "*") s.push(left * right);
            else if(token == "/") s.push(left / right);
        } else {
            s.push(stoi(token));
        }
    }
    return s.top();
}

2.4 stack的模拟实现

cpp

复制代码
#include <vector>
#include <deque>

namespace my {
    template<class T, class Container = std::deque<T>>
    class stack {
    public:
        stack() {}
        
        void push(const T& x) { _c.push_back(x); }
        void pop() { _c.pop_back(); }
        T& top() { return _c.back(); }
        const T& top() const { return _c.back(); }
        size_t size() const { return _c.size(); }
        bool empty() const { return _c.empty(); }
        
    private:
        Container _c;
    };
}

🚶 三、queue(队列)

3.1 队列的基本概念

队列是一种**先进先出(FIFO)**的数据结构,元素从队尾入队,从队头出队。

3.2 队列的接口

函数声明 接口说明
queue() 构造空的队列
empty() 检测队列是否为空
size() 返回队列中有效元素的个数
front() 返回队头元素的引用
back() 返回队尾元素的引用
push(const T& val) 在队尾将元素val入队列
pop() 将队头元素出队列

3.3 经典应用:用队列实现栈

cpp

复制代码
class MyStack {
private:
    queue<int> q;
    
public:
    void push(int x) {
        int n = q.size();
        q.push(x);
        // 将前面的元素重新入队,使得新元素在队头
        for(int i = 0; i < n; i++) {
            q.push(q.front());
            q.pop();
        }
    }
    
    void pop() { q.pop(); }
    int top() { return q.front(); }
    bool empty() { return q.empty(); }
};

3.4 queue的模拟实现

cpp

复制代码
#include <list>
#include <deque>

namespace my {
    template<class T, class Container = std::deque<T>>
    class queue {
    public:
        queue() {}
        
        void push(const T& x) { _c.push_back(x); }
        void pop() { _c.pop_front(); }
        T& front() { return _c.front(); }
        const T& front() const { return _c.front(); }
        T& back() { return _c.back(); }
        const T& back() const { return _c.back(); }
        size_t size() const { return _c.size(); }
        bool empty() const { return _c.empty(); }
        
    private:
        Container _c;
    };
}

🏆 四、priority_queue(优先级队列)

4.1 优先级队列的基本概念

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

4.2 priority_queue的接口

函数声明 接口说明
priority_queue() 构造空的优先级队列
priority_queue(first, last) 用迭代器范围构造优先级队列
empty() 检测优先级队列是否为空
top() 返回优先级队列中最大(最小)元素
push(x) 在优先级队列中插入元素x
pop() 删除优先级队列中最大(最小)元素

4.3 使用示例

cpp

复制代码
#include <queue>
#include <vector>
#include <functional>  // for greater

void TestPriorityQueue() {
    vector<int> v{3, 2, 7, 6, 0, 4, 1, 9, 8, 5};
    
    // 默认大顶堆
    priority_queue<int> maxHeap;
    for(auto& e : v) maxHeap.push(e);
    cout << "大顶堆堆顶: " << maxHeap.top() << endl;  // 9
    
    // 小顶堆
    priority_queue<int, vector<int>, greater<int>> minHeap(v.begin(), v.end());
    cout << "小顶堆堆顶: " << minHeap.top() << endl;  // 0
}

4.4 自定义类型在priority_queue中的使用

cpp

复制代码
class Date {
public:
    Date(int year = 1900, int month = 1, 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._day && _day > d._day);
    }
    
    friend ostream& operator<<(ostream& _cout, const Date& d) {
        _cout << d._year << "-" << d._month << "-" << d._day;
        return _cout;
    }
    
private:
    int _year, _month, _day;
};

void TestDatePriorityQueue() {
    // 大顶堆,需要提供 operator<
    priority_queue<Date> maxHeap;
    maxHeap.push(Date(2018, 10, 29));
    maxHeap.push(Date(2018, 10, 28));
    maxHeap.push(Date(2018, 10, 30));
    cout << "日期大顶堆堆顶: " << maxHeap.top() << endl;  // 2018-10-30
    
    // 小顶堆,需要提供 operator>
    priority_queue<Date, vector<Date>, greater<Date>> minHeap;
    minHeap.push(Date(2018, 10, 29));
    minHeap.push(Date(2018, 10, 28));
    minHeap.push(Date(2018, 10, 30));
    cout << "日期小顶堆堆顶: " << minHeap.top() << endl;  // 2018-10-28
}

4.5 经典应用:数组中第K个最大元素

cpp

复制代码
int findKthLargest(vector<int>& nums, int k) {
    // 创建小顶堆,保持堆的大小为k
    priority_queue<int, vector<int>, greater<int>> minHeap;
    
    for(int num : nums) {
        minHeap.push(num);
        if(minHeap.size() > k) {
            minHeap.pop();  // 删除最小的元素
        }
    }
    
    return minHeap.top();  // 堆顶就是第K个最大元素
}

4.6 priority_queue的模拟实现(简化版)

cpp

复制代码
#include <vector>
#include <functional>

namespace my {
    template<class T, class Container = vector<T>, 
             class Compare = less<typename Container::value_type>>
    class priority_queue {
    public:
        priority_queue() = default;
        
        template<class InputIterator>
        priority_queue(InputIterator first, InputIterator last)
            : _c(first, last) {
            make_heap(_c.begin(), _c.end(), _comp);
        }
        
        void push(const T& x) {
            _c.push_back(x);
            push_heap(_c.begin(), _c.end(), _comp);
        }
        
        void pop() {
            pop_heap(_c.begin(), _c.end(), _comp);
            _c.pop_back();
        }
        
        const T& top() const { return _c.front(); }
        bool empty() const { return _c.empty(); }
        size_t size() const { return _c.size(); }
        
    private:
        Container _c;
        Compare _comp;
    };
}

🔄 五、底层容器与deque

5.1 为什么选择deque作为默认底层容器?

STL中stack和queue默认使用deque作为底层容器,主要原因是:

  1. 效率平衡:deque在头尾插入/删除都是O(1)时间复杂度

  2. 内存效率:不需要像vector那样大量搬移元素

  3. 空间利用率:比list更高效,不需要额外指针开销

  4. 避免遍历:stack和queue不需要遍历操作,deque遍历效率低的缺点不影响

5.2 deque的结构特点

deque(双端队列)不是真正连续的空间,而是由分段连续的小空间拼接而成:

  • 支持快速随机访问

  • 头尾插入删除效率高

  • 空间利用率高

  • 迭代器设计复杂

5.3 不同底层容器的选择

cpp

复制代码
// stack可以使用vector、list、deque
stack<int, vector<int>> s1;      // 适合随机访问,尾部操作
stack<int, list<int>> s2;        // 适合频繁插入删除
stack<int, deque<int>> s3;       // 默认选择,综合性能好

// queue可以使用list、deque(不能用vector,因为需要pop_front)
queue<int, list<int>> q1;        // 适合频繁插入删除
queue<int, deque<int>> q2;       // 默认选择

🎯 六、选择指南与最佳实践

6.1 何时使用stack?

  • 需要后进先出(LIFO)的场景

  • 括号匹配、表达式求值

  • 函数调用栈、撤销操作

  • 深度优先搜索(DFS)

6.2 何时使用queue?

  • 需要先进先出(FIFO)的场景

  • 广度优先搜索(BFS)

  • 任务调度、消息队列

  • 缓冲区管理

6.3 何时使用priority_queue?

  • 需要快速获取最大/最小元素的场景

  • 任务优先级调度

  • 求第K大/第K小元素

  • Dijkstra算法等图算法

6.4 性能对比

操作 stack queue priority_queue
插入 O(1) O(1) O(log n)
删除 O(1) O(1) O(log n)
访问顶部 O(1) O(1) O(1)
查找元素 不支持 不支持 不支持

📝 七、总结

C++ STL中的容器适配器(stack、queue、priority_queue)为我们提供了高效、易用的数据结构实现:

  1. stack:专注于后进先出操作,适合需要"撤销"或"回退"的场景

  2. queue:管理先进先出序列,适合任务调度和缓冲区

  3. priority_queue:基于堆实现,快速获取最大/最小元素

这些容器适配器的设计体现了单一职责原则适配器模式的精髓,它们:

  • 提供简洁的接口,隐藏复杂实现

  • 基于现有容器,代码复用性高

  • 通过模板参数灵活选择底层容器

  • 在实际开发中广泛应用,提升代码质量

理解这些容器的底层实现、适用场景和性能特点,能够帮助我们在实际编程中做出更合适的选择,编写出更高效、更易维护的代码。

相关推荐
山土成旧客2 小时前
【Python学习打卡-Day30】模块化编程:从“单兵作战”到“军团指挥”
开发语言·python·学习
lingzhilab2 小时前
零知IDE——零知ESP32-S3部署 AI小智 2.0版发布!调整界面UI,新增LED、舵机和风扇外部设备和控制
ide·交互
byzh_rc2 小时前
[算法设计与分析-从入门到入土] 分治法
算法
世转神风-2 小时前
qt-union-联合体基础讲解
开发语言·qt
moxiaoran57532 小时前
Go语言的数据类型转换
开发语言·后端·golang
秋邱2 小时前
Java包装类:基本类型与包装类转换、自动装箱与拆箱原理
java·开发语言·python
海上彼尚2 小时前
Go之路 - 8.go的接口
开发语言·golang·xcode
乐茵lin2 小时前
golang context底层设计探究
开发语言·后端·golang·大学生·设计·context·底层源码
lkbhua莱克瓦242 小时前
基础-约束
android·开发语言·数据库·笔记·sql·mysql·约束