STL 中常见容器介绍,及容器适配器(队列、栈、优先级队列)典型力扣题 c++

        STL 中常见的容器主要有三种:顺序容器、关联式容器、容器适配器,三种类型容器特性分别如下:

一、 顺序容器

        容器并非排序的,元素的插入位置同元素的值无关。包含vector deque list ,具体实现原理
如下:
  • vector 动态数组
        元素在内存 连续存放,支持快速随机访问 在中间进行插入和删除会造成内存块的拷贝,另外,当插入较多的元素后,预留内存空间可能不够,需要重新申请一块足够大的内存并把原来的数据拷贝到新的内存空间。
#include<vector>
//vector是动态数组,在尾部增删的复杂度是O(1)
vector<int> vec;        //声明一个int型向量
vector<int> vec(5);     //声明一个初始大小为5的int向量
vector<int> vec(10, 1); //声明一个初始大小为10且值都是1的向量
vector<int> vec(tmp);   //声明并用tmp向量初始化vec向量
vector<int> tmp(vec.begin(), vec.begin() + 3);  //用向量vec的第0个到第2个值初始化tmp
int arr[5] = {1, 2, 3, 4, 5};   
vector<int> vec(arr, arr + 5);      //将arr数组的元素用于初始化vec向量
//说明:当然不包括arr[4]元素,末尾指针都是指结束元素的下一个元素,
//这个主要是为了和vec.end()指针统一。
vector<int> vec(&arr[1], &arr[4]); //将arr[1]~arr[4]范围内的元素作为vec的初始值
 
 
向量大小: vec.size();
向量最大容量: vec.max_size();
更改向量大小: vec.resize();
向量真实大小: vec.capacity();
向量判空: vec.empty();
减少向量大小到满足元素所占存储空间的大小: vec.shrink_to_fit(); //shrink_to_fit
 
 
多个元素赋值: vec.assign(); //类似于初始化时用数组进行赋值
末尾添加元素: vec.push_back();
末尾删除元素: vec.pop_back();
任意位置插入元素: vec.insert();//例如vec.insert(vec.begin(),1);
任意位置删除元素: vec.erase();
交换两个向量的元素: vec.swap();
清空向量元素: vec.clear();
 
 
开始指针:vec.begin();
末尾指针:vec.end(); //指向最后一个元素的下一个位置
指向常量的开始指针: vec.cbegin(); //意思就是不能通过这个指针来修改所指的内容,但还是可以通过其他方式修改的,而且指针也是可以移动的。
指向常量的末尾指针: vec.cend();
 
 
下标访问: vec[1]; //并不会检查是否越界
at方法访问: vec.at(1); //以上两者的区别就是at会检查是否越界,是则抛出out of range异常
访问第一个元素: vec.front();
访问最后一个元素: vec.back();
返回一个指针: int* p = vec.data(); //可行的原因在于vector在内存中就是一个连续存储的数组,所以可以返回一个指针指向这个数组。这是是C++11的特性.
很方便地求和:accumulate(vec.begin(),vec.end(),0);
  • list 双向链表
        元素在内存不连续存放,不支持随机存取,通过指针来进行数据的访问,这个特点使得它的随即存取变的非常没有效率,因此它没有提供[ ]操作符的重载。但由于链表的特点,它可以以很好的效率支持任意地方的删除和插入。
#include <list>

//List定义和初始化:
list<int>lst1;          //创建空list
list<int> lst2(5);       //创建含有5个元素的list
list<int>lst3(3,2);  //创建含有3个元素的list
list<int>lst4(lst2);    //使用lst2初始化lst4
list<int>lst5(lst2.begin(),lst2.end());  //同lst4

//List常用操作函数:
Lst1.back()     //返回最后一个元素
Lst1.begin()     //返回指向第一个元素的迭代器
Lst1.front()     //返回第一个元素
Lst1.end()     //返回末尾的迭代器

Lst1.clear()     //删除所有元素
Lst1.empty()     //如果list是空的则返回true
Lst1.erase()     //删除一个元素
Lst1.pop_back()     //删除最后一个元素
Lst1.pop_front()     //删除第一个元素
Lst1.push_back()     //在list的末尾添加一个元素
Lst1.push_front()     //在list的头部添加一个元素

Lst1.max_size()      //返回list能容纳的最大元素数量
Lst1.resize()      //改变list的大小
Lst1.reverse()      //把list的元素倒转
Lst1.size()      //返回list中的元素个数
Lst1.sort()      //给list排序
Lst1.unique()      //删除list中重复的元素
  • deque 双向队列
        元素在内存 连续存放 ,随机存取任何元素都能在常数时间完成(仅次于vector )。在两端增删元素具有较佳的性能(大部分情况下是常数时间)。deque是stack和queue默认的底层实现容器
deque<int> dq;
dq.push_back(1);    //队尾加元素
dq.push_front(1);   //队首加元素
dq.pop_back();   //队尾出队
dq.pop_front();   //队首出队

        实际使用时,遵循的原则
1. 如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
2. 如果你需要大量的插入和删除,而不关心随即存取,则应使用list
3. 如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque。

二、关联式容器

        元素是排序的;插入任何元素,都按相应的排序规则来确定其位置;在查找时具有非常好的性 能;通常以平衡二叉树的方式实现。包含set multiset map multimap ,具体实现原理如下:
  • set/multiset 头文件
        set 即集合。 set 中不允许相同元素, multiset 中允许存在相同元素。
  • map/multimap 头文件
        map与 set 的不同在于 map 中存放的元素有且仅有两个成员变,一个名为 first, 另一个名为
second, map根据 first 值对元素从小到大排序,并可快速地根据 first 来检索元素。
        
        具体操作在我之前的博客哈希法中有讲解: 哈希法c++_June的博客-CSDN博客

三、容器适配器

        封装了一些基本的容器,使之具备了新的函数功能,比如把deque 封装一下变为一个具 stack 功能的数据结构。这新得到的数据结构就叫适配器。包含stack,queue,priority_queue ,具体实现
原理如下:
  • stack 栈
        后进先出。栈的底层实现vector,deque,list 都是可以的,STL标准库默认是以deque为缺省情况实现的。不提供迭代器(iterator)进行遍历。
#include<stack>
 
stack<int> s;
s.push(item);		//将item压入栈顶
s.pop();			//删除栈顶的元素,但不会返回
s.top();			//返回栈顶的元素,但不会删除
s.size();			//返回栈中元素的个数
s.empty();			//检查栈是否为空,如果为空返回true,否则返回false 
  • queue 队列
        先进先出。STL标准库默认是以deque为缺省情况实现的。同样不提供迭代器(iterator)进行遍历。
queue<TreeNode*> q;
q.push();
q.pop();

q.front() //访问queue队首元素
q.back() //访问queue队尾元素

q.empty() //判断queue队列空,当队列空时,返回true。
q.size() //访问队列中的元素个数
  • priority_queue 优先级队列
        内部维持某种有序,然后确保优先级最高的元素总是位于头部。最高优先级元素总是第一个出列。大顶堆(堆头是最大元素),小顶堆(堆头是最小元素)一般使用  priority_queue 进行实现,且默认情况下是大顶堆(比较函数为less
priority_queue<Type, Container, Functional>
//Type 是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 是比较的方式。

//创建大顶堆、小顶堆
priority_queue<int, vector<int>, less<int>> q; //大顶堆
priority_queue<int, vector<int>, greater<int>> q; //小顶堆

//若存入元素是pair,先比较pair的first元素,first元素相等时,再比较second元素
priority_queue<pair<int,int>,vector<pair<int,int> >,less<pair<int,int> > > q;

//自定义类型有两种方式:运算符重载 和 重写仿函数

struct cmp//重写仿函数
{
    bool operator() (int a ,int b) 
    {
        return a<b; //大顶堆
    }
};
priority_queue<int, vector<int>, cmp> q; //大顶堆

四、容器适配器相关 力扣题型

232. 用栈实现队列        

        请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

        实现 MyQueue 类:

        void push(int x) 将元素 x 推到队列的末尾
        int pop() 从队列的开头移除并返回元素
        int peek() 返回队列开头的元素
        oolean empty() 如果队列为空,返回 true ;否则,返回 false

解题思路:

        栈模拟队列的行为,需要两个栈一个输入栈,一个输出栈。在push数据的时候,只要数据放进输入栈就好,但在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入),再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。

class MyQueue {
public:
    stack<int> instk; //输入栈
    stack<int> outstk;  //输出栈
    MyQueue() {
    }
    
    void push(int x) {
        instk.push(x);
    }
    
    int pop() {
        if(outstk.empty()){
            while(!instk.empty()){
                outstk.push(instk.top());
                instk.pop();
            }
        }
        int out=outstk.top();
        outstk.pop();
        return out;
    }
    
    int peek() {
        if(outstk.empty()){
            while(!instk.empty()){
                outstk.push(instk.top());
                instk.pop();
            }
        }
        return outstk.top();
    }
    
    bool empty() {
        return outstk.empty() && instk.empty();
    }
};

225. 用队列实现栈

        请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

        实现 MyStack 类:

        void push(int x) 将元素 x 压入栈顶。
        int pop() 移除并返回栈顶元素。
        int top() 返回栈顶元素。
        boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

解题思路:

        用两个队列q1和q2实现栈的功能,但此题和上题的输入输出栈不同,q2只是起到一个备份的作用,pop操作时把q1队尾元素以外的元素都备份到q2,然后弹出队尾元素,再把其他元素从q2存回q1。

class MyStack {
public:
    queue<int> q1;
    queue<int> q2;//q2用来备份
    MyStack() {

    }
    
    void push(int x) {
        q1.push(x);
    }
    
    int pop() {
        int out;
        while(!q1.empty()){ //把q1的元素全部放到q2(备份队列)
            q2.push(q1.front());
            q1.pop();
        }
        while(!q2.empty()){ //把备份队列中除了队尾的元素全部放回q1
            out=q2.front();
            q2.pop();
            if(!q2.empty()){
                q1.push(out);
            }
        }
        return out;
    }
    
    int top() {
        return q1.back();
    }
    
    bool empty() {
        return q1.empty();
    }
};

239. 滑动窗口最大值

        给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。

示例 1:        输入:nums = [1,3,-1,-3,5,3,6,7], k = 3        输出:[3,3,5,5,6,7]

解题思路:

        此题是自己实现一个单调队列,即单调递减或单调递增的队列,因此只用访问que.front()就可以返回当前窗口的最大值。

        那么单调队列的  pop 和  push 函数具体应该如何实现呢?我们可以根据以下规则进行设计:        

        pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作

        push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止

滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

class Solution {
private:
    class myqueue{
        public:
            deque<int> q;
            void pop(int value){
                if(!q.empty() && q.front()==value){
                    q.pop_front();
                }
            }
            void push(int value){
                while(!q.empty() && q.back()<value){
                    q.pop_back();
                }
                q.push_back(value);
            }
            int front(){
                return q.front();
            }

    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n=nums.size();
        myqueue que;
        vector<int> res;
        for(int i=0;i<k;i++){
            que.push(nums[i]);
        }
        res.push_back(que.front());
        for(int i=0;i+k<n;i++){
            que.pop(nums[i]);
            que.push(nums[i+k]);
            res.push_back(que.front());
        }
        return res;
    }
};

​​​​​​347. 前 K 个高频元素

        给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:        输入: nums = [1,1,1,2,2,3], k = 2        输出: [1,2]

解题思路:

        此题采用小顶堆进行实现,每加入一组和堆顶元素的 second 进行比较,若比它大则弹出堆顶元素并将此元素加入小顶堆,维持小顶堆的大小不变,遍历到最后里面存的就是前k个高频元素了。

class Solution {
public:
    struct cmp{ //自定义比较函数
        bool operator()(const pair<int, int>& m, const pair<int, int>& n) {
            return m.second > n.second;
        }
    };
    
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> occurrences;
        for (auto& v : nums) {
            occurrences[v]++;
        }

        // pair 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> q;
        for (auto& [num, count] : occurrences) {
            if (q.size() == k) {
                if (q.top().second < count) {
                    q.pop();
                    q.emplace(num, count);
                }
            } else {
                q.emplace(num, count);
            }
        }
        vector<int> ret;
        while (!q.empty()) {
            ret.emplace_back(q.top().first);
            q.pop();
        }
        return ret;
    }
};

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值