C++栈与队列的底层实现(包括单调队列、优先级队列等)

容器和容器适配器

在C++中,容器包括:
顺序容器:vector、string、list、deque等;
关联容器:set、map等;
适配器是指将一个类型的接口转化为另外一个接口。C++中的容器适配器的意思便是使用C++提供的底层容器,在底层容器的基础上实现我们所希望的功能,而向外表现出和容器一样的特点。
栈stack、队列queue、优先级队列priority_queue都属于容器适配器,而非容器。
既然容器适配器是在一个底层容器的基础上实现的,那么stack和queue也都有相应的底层容器,在缺省的情况下二者默认使用的都是deque,也即双端队列

双端队列deque

顾名思义,deque是在队列的两端都可以进行插入和删除的队列,且增删的效率均为O(1)。
deque支持随机访问,在表面上看起来是连续存储的,但在底层实现中缺不是完全连续,而是分段连续的。
deque有一个中控器,中控器中包含了每一段连续地址的大小、开始地址(迭代器)、结束地址(迭代器),并包含一个指向开始地址的指针。
在这里插入图片描述

由上图的结构可以知道,实际访问到一个地址的元素,需要先访问中控器,找到相应的缓冲区,再在缓冲区中找到目标元素。因此deque的随机访问是比vector低效的。
关于deque的优缺点,这里借鉴@bang_bang 的总结,非常全面:
在这里插入图片描述
即使有上面的缺点,其仍适合用作stack和queue的底层容器,这是因为deque的缺点主要来自于不适合遍历,而stack和queue本身是不需要对成员进行遍历的,stack只对栈顶元素访问和处理,queue只对队头和队尾的元素进行访问和处理,因此十分合适。

使用其他容器作为底层容器

在平常使用stack或queue时,可以直接使用如下方式:

std::stack<int> myStack;  // 默认使用deque为底层容器的栈
std::queue<int> myQue;  // 默认使用deque为底层容器的队列

也可以根据需要指定底层容器的类型:

std::stack<int, std::vector<int> > myStack2;  // 使用vector为底层容器的栈
std::queue<int, std::vector<int> > myQue2;  // 使用vector为底层容器的队列
std::stack<int, std::list<int> > myStack3;  // 使用list为底层容器的栈
std::queue<int, std::list<int> > myQue3;  // 使用list为底层容器的队列

只要支持以下几种操作的容器均可作为底层容器:

empty():检测容器是否为空
size():返回容器中有效元素个数
front():返回容器中第一个元素的引用
push_back():在容器尾部插入元素
pop_back():删除容器尾部元素

优先级队列(priority_queue)

优先级队列也是一个容器适配器,其可选的底层容器包括vector、deque等,但注意不能使用list,STL默认使用vector。
优先级队列的实现类似于堆(默认为大顶堆)其堆顶元素一定是所有元素中最大的一个,对应于队列来说,其在一端插入元素,在另一端弹出元素,每次弹出的元素一定是最大的元素。
使用:

priority_queue <int> q; // 默认情况下,使用vector作为底层容器,并且为大顶堆
priority_queue <int, vector<int>, less<int>> q;// 和默认情况等价,第一项指定元素类型,第二项指定底层容器类型,第三项仿函数用于指定堆下沉时比较方法,less即为小于堆顶元素时下沉
priority_queue <int, vector<int>, greater<int>> q; // greater指定大于堆顶元素时下沉,即维护小顶堆
// 需要注意的是,如果要手动指定仿函数,greater和less在头文件#include <functional>中

经典题目

在这里插入图片描述
思路:使用map统计每个数出现的频率(考虑到效率,使用底层为哈希表的unordered_map),再利用优先级队列(大顶堆)将map中的元素对入队,每次弹出堆顶元素对即为频率最高的元素,弹k次即可。
(也可以维护小顶堆,控制优先级队列大小始终不超过k,即始终保留频率最高的k个元素,这么做可以更省空间)

class Solution {
public:
    // 由于使用优先级队列时是比较map的value部分,需要手动重载比较仿函数,默认的greater和less只会比较key
    class mycomparison {
    public:
        // 小于时返回true,等价于less,维护大顶堆
        bool operator()(const pair<int, int> &lhs, const pair<int, int> &rhs) {
            return lhs.second < rhs.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 使用map统计每个数出现的次数
        unordered_map<int, int> myMap;
        for (int i = 0; i < nums.size(); i++) {
            myMap[nums[i]]++;
        }
        // 优先级队列,每次查找map中频率最高的数,大顶堆查k次,也可以用小顶堆始终维护大小为k的堆
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> myQue;
        for (unordered_map<int, int>::iterator it = myMap.begin(); it != myMap.end(); it++) {
            myQue.push(*it);
        }
        // 对于大顶堆,弹k次堆顶元素即可(也即优先级队列k次出队)
        vector<int> result(k);
        for(int i = 0; i < k; i++) {
            result[i] = myQue.top().first;
            myQue.pop();
        }
        return result;
    }
};
  • 8
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值