C++学习之STL学习:stack\queue\priority_queue

本期我们将深入学习C++中关于STL容器的容器类型:stack(栈)和deque(队列)

作者本人的gitee:楼田莉子 (riko-lou-tian) - Gitee.com

目录

stack的介绍和官方文档

stack的构造函数​编辑

stack相关的函数

empty(判空)

push(入栈)

pop(出栈)

[top(取栈顶元素 )](#top(取栈顶元素 ))

size​编辑

emplace​编辑

swap

stack的模拟实现

stack相关题目

1、最小栈

2、栈的压入弹出序列

3、逆波兰表达式求值

4、用栈实现队列

5、二叉树层序遍历

queue的介绍和官方文档

queue构造函数

queue相关的函数

empty

size

front

back

push(入队列)

pop(出队列)

emplace

[​编辑 swap](#编辑 swap)

queue的模拟实现

deque的介绍和官方文档

deque的特殊之处:为什么模拟实现用deque

以下是deque的功能介绍,了解即可。

deque初始

构造

析构

=运算符重载

迭代器

正向

​编辑

反向

const正向

const反向

容器

size

max_size

resize

empty

shrink_to_fit

访问队列

[]运算符重载

at

front

back

deque的成员函数

push_back

push_front

pop_back

pop_front

insert

erase

[​编辑 swap](#编辑 swap)

clear

[​编辑 emplace](#编辑 emplace)

emplace_front

​编辑

emplace_back

deque非成员函数

关系运算符重载

swap(双队列)

priority_queue的介绍和官方文档

priority_queue的相关函数

构造函数

empty

size

top

push

pop

emplace

swap

priority_queue相关题目

数组中第K个最大元素

priority_queue的模拟实现

仿函数(重点!!!)


stack的介绍和官方文档

官方文档: stack - C++ 参考

stack的介绍

stack的构造函数

stack不支持迭代器遍历,因此stack也不支持范围for。

stack相关的函数

empty(判空)

push(入栈)

pop(出栈)

top(取栈顶元素 )

测试:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<stack>
using namespace std;
void test1()
{
	stack<int> s;
	s.push(1);
	s.push(2);
	s.push(3);
	s.push(4);
	while (!s.empty())
	{
		cout << s.top() << " ";
		s.pop();
	}
	cout << endl;
}
int main()
{
	test1();
	return 0;
}

结果为:

size

emplace

swap

stack的模拟实现

cpp 复制代码
#pragma once
#include <iostream>
#include<vector>
#include<list>
#include<deque>
using namespace std;
namespace Boogiepop
{
	//适配器/配接器               //用缺省值可以不传递第二个模板参数
	template<class T, class container=deque<T>>
	//适配器是用来实现转换的
	//本质上是容器适配器
	//用容器适配转换出来的
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_back();
		}
		size_t size() const
		{
			return _con.size();
		}
		bool empty() const
		{
			return _con.empty();
		}
		const T& top() const
		{
			return _con.back();
		}
	private:
		container _con;
	};

stack相关题目

1、最小栈

题目中"在常数时间内获取最小栈"意思是getmin()函数的时间复杂度是O(1).

答案:

cpp 复制代码
class MinStack {
public:
    MinStack() 
    {
        
    }
    
    void push(int val) 
    {
        _st.push(val);
        if(_minst.empty()||val<=_minst.top())
        {
            _minst.push(val);
        }
    }
    
    void pop() 
    {
        if(_st.top()==_minst.top())
        {
            _minst.pop();
        }
        _st.pop();
    }
    
    int top() 
    {
        return _st.top();
    }
    
    int getMin() 
    {
        return _minst.top();
    }
    private:
    stack<int> _st;
    stack<int> _minst;
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(val);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

2、栈的压入弹出序列

本题中从大小关系入手是没有意义的。

思路是模拟出入栈顺序跟出栈顺序匹配

思路如下:

1、pushi指向的数据入栈

2、持续地让栈中数据与popi比较。如果想等则popi++,出栈顶数据,直到为空或者不匹配。

3、重复1、2 步

核心原则:持续比较

结束条件:pushi走到尾部

cpp 复制代码
class Solution 
{
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pushV int整型vector 
     * @param popV int整型vector 
     * @return bool布尔型
     */
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) 
    {
        // write code here
        stack<int> st;
        size_t pushi=0,popi=0;
        while(pushi<pushV.size())
        {
            //先入栈
            st.push(pushV[pushi++]);
            //尝试与出栈序列匹配
            while(!st.empty()&&st.top()==popV[popi])
            {
                popi++;
                st.pop();
            }
        }
        return st.empty();
    }
};

3、逆波兰表达式求值

逆波兰表达式的解释和扩展:【数据结构】你知道波兰表达式和逆波兰表达式吗?我才知道原来栈在表达式求值中还能这样使用......-腾讯云开发者社区-腾讯云 简单来说,计算机的运算表达式有三种:

中缀表达式(最常用的):运算数+运算符+运算数

前缀表达式:运算符+运算数+运算数

后缀表达式:运算数+运算数+运算符

所以本质上逆波兰表达式就是后缀表达式

后缀表达式运算符按优先级排列,而且要挨着要运算的运算数。

思路:

1、运算数入栈

2、如果是运算符,则出栈顶的两个数据,然后继续入栈

这里额外科普一个string的应用:将string转化为整数

文档:stoi - C++ 参考

答案:

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) 
    {
        stack<int> st;
        for(auto& str:tokens)
        {
            if(str=="+"||str=="-"||str=="*"||str=="/")
            {
                //遇到运算符要出栈两个运算数然后运算后入栈
                int right=st.top();
                st.pop();
                int left=st.top();
                st.pop();
                switch(str[0])
                {
                    case '+':
                    st.push(left+right);
                    break;
                    
                    case '-':
                    st.push(left-right);
                    break;
                    case '*':
                    st.push(left*right);
                    break;
                    case '/':
                    st.push(left/right);
                    break;
                    default:
                    break;
                }
            }
            else
            {
                //运算数入栈
                st.push(stoi(str));
            }
        }        
        return st.top();
    }
};

4、用栈实现队列

5、二叉树层序遍历

cpp 复制代码
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) 
    {
        queue<TreeNode*>q;
        int levelsize=0;//记录当前层数多少个数据
        if(root)
        {
            q.push(root);
            levelsize=1;
        }
        vector<vector<int>>vv;
        //层序遍历
        while(!q.empty())
        {
            vector<int>v;
            //控制一层一层出
            while(levelsize--)
            {   
                TreeNode*front=q.front();
                q.pop();
                v.push_back(front->val);
                if(front->left)
                {
                    q.push(front->left);
                }
                if(front->right)
                {
                    q.push(front->right);
                }
            }
            vv.push_back(v);
            //当前层出完了下一层节点都进队列,队列数据个数就是下一次节点个数
            levelsize=q.size();
        }
        return vv;
    }
};

这段代码的时间复杂度不是O(N^2)。因为队列进入和出只用了N次。所以实际上时间复杂度是O(N)

queue的介绍和官方文档

官方文档:

queue - C++ 参考

queue(单端队列)的介绍

queue构造函数

queue相关的函数

empty

size

front

back

push(入队列)

pop(出队列)

emplace

swap

测试:

cpp 复制代码
void test1()
{
	queue<int> q;
	q.push(1);
	q.push(2);
	q.push(3);
	cout << q.front() << endl;

	while (!q.empty())
	{
		cout << q.front() << " ";
		q.pop();
	}
	cout << endl;
}
int main()
{
	test1();
	return 0;
}

答案:

queue的模拟实现

cpp 复制代码
#pragma once
#include <iostream>
#include<vector>
#include<list>
#include<deque>
using namespace std;
namespace Boogiepop
{
	//适配器/配接器               //用缺省值可以不传递第二个模板参数
	template<class T, class container = deque<T>>
	//适配器是用来实现转换的
	//本质上是容器适配器
	//用容器适配转换出来的
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			//_con.erase(_con.begin());效率比较低
			_con.pop_front();
		}
		size_t size() const
		{
			return _con.size();
		}
		bool empty() const
		{
			return _con.empty();
		}
		const T& top() const
		{
			return _con.back();
		}
	private:
		container _con;
	};
}

deque的介绍和官方文档

官方文档:​​​​​​deque - C++ 参考

std::d eque - cppreference.com

deque(双端队列)的介绍

deque的结构图

deque的特殊之处:为什么模拟实现用deque

如上图所示,deque在功能上拥有vector和list的结合。

但是deque遍历的性能并不高效

vector

优点:

1下标随机访问,快;

2尾插尾删效率高

3、CPU高速缓存命中率高

缺点:

1头部、中间插入删除效率低下;

2插入空间不够要扩容,扩容有一定性能消耗,倍数扩容有一定空间浪费

list

优点:

1、任意位置均为O(1)复杂度的插入删除

2、按需申请释放内存

缺点:

1、不支持下标随机访问

2、CPU高速缓存命中率低,还存在缓存污染。

deque

优点:适合头尾插入删除,效率高;下标随机访问效率还不错(大量数据处理不如vector)

缺点:中间位置插入删除效率低,需要大量挪动数据。

相比于vector和list,性能都不够极致

\]下标随机访问的效率: list\<\ #include using namespace std; int main() { priority_queue pq; //数据大的优先级高; //优先级高的优先出队列 pq.push(5); pq.push(3); pq.push(8); pq.push(1); pq.push(6); while (!pq.empty()) { cout << pq.top() << " "; pq.pop(); } return 0; } ``` 结果为: ![](https://i-blog.csdnimg.cn/direct/21bdb5b83d66466087fbb2596703a4b2.png) 默认情况下priority_queue是大的数据优先级高。如果需要小的数据优先级高则需要通过仿函数控制。 而如下图所示 ![](https://i-blog.csdnimg.cn/direct/adee664fb57d415b83f363452413c27d.png) 默认传递的是小于less的仿函数的时候,大的优先级高;因此反过来说,当我们传大于greater的仿函数的时候,小的优先级高 less和greater是标准库内的仿函数 官方文档: [](https://legacy.cplusplus.com/reference/functional/less/ "less - C++ 参考") [greater - C++ 参考](https://legacy.cplusplus.com/reference/functional/greater/ "greater - C++ 参考") priority_queue ```cpp void test_priority_queue() { priority_queue pq1; //数据大的优先级高; //默认情况下优先级高的优先出队列 //控制小的数据优先级高需要仿函数 priority_queue, greater> pq2; //less和greater是标准库函数 //greater是仿函数,表示优先级高的元素优先出队列 //小的元素优先级高,所以优先出队列 pq1.push(5); pq1.push(3); pq1.push(8); pq1.push(1); pq1.push(6); while (!pq1.empty()) { cout << pq1.top() << " "; pq1.pop(); } cout << endl; pq2.push(5); pq2.push(3); pq2.push(8); pq2.push(1); pq2.push(6); while (!pq2.empty()) { cout << pq2.top() << " "; pq2.pop(); } cout << endl; } ``` 结果为: ![](https://i-blog.csdnimg.cn/direct/5c31c69465bb45c3b9e99e9e16db5f76.png) vector也有类似的用法 ```cpp void test_vector() { vectorv = {6,7,4,1,3,5 }; //vector的sort默认小的优先级高 //< 升序 sort(v.begin(), v.end()); for (auto& e : v) { cout << e << " "; } cout << endl; //> 降序 sort(v.begin(), v.end(), greater()); for (auto& e : v) { cout << e << " "; } cout << endl; } ``` 结果为: ![](https://i-blog.csdnimg.cn/direct/a6e981fbd0484727b1bf2917dd09d906.png) 通过上面两段代码,我们发现这两者第三个参数传的是不一样的。 为什么算法库的sort第三个参数跟仿函数不一样呢? ![](https://i-blog.csdnimg.cn/direct/d306ff652c154304a76a3149582d4482.png) ![](https://i-blog.csdnimg.cn/direct/89178e1dfd48428e9d40728add25d236.png) 我们会发现,sort第三个参数类型是对象,而priority_queue传递的是类模板参数。 ## priority_queue的相关函数 ### 构造函数 ![](https://i-blog.csdnimg.cn/direct/272de2ae1c79459295b127ba600a6d31.png) #### empty ![](https://i-blog.csdnimg.cn/direct/bd3b3ae13c9a47e1881f6eff86559596.png) ### size ![](https://i-blog.csdnimg.cn/direct/955998c735d94b37bd259a0ecda098d2.png) ### top ![](https://i-blog.csdnimg.cn/direct/e7700956e4204623b2496714ff5cb2b1.png) ### push ![](https://i-blog.csdnimg.cn/direct/f75b98de720f4eceb5b86facb8f4d87f.png) ### pop ![](https://i-blog.csdnimg.cn/direct/bf81b7cb1fb3491588ea2bb5bad6e123.png) ### emplace ![](https://i-blog.csdnimg.cn/direct/d2c241c1081947cea867494506b5e5d6.png) ### swap ![](https://i-blog.csdnimg.cn/direct/bff4e609c626455096f3bcbc60bab6f9.png) ## priority_queue相关题目 ### 数组中第K个最大元素 ![](https://i-blog.csdnimg.cn/direct/783584ffee01451aa4adeef0014bab0c.png) ```cpp class Solution { public: int findKthLargest(vector& nums, int k) { priority_queuepq(nums.begin(),nums.end()); while(--k) { pq.pop(); } return pq.top(); } }; ``` ## priority_queue的模拟实现 逻辑结构图: ![](https://i-blog.csdnimg.cn/direct/d862b00ad0224e60a66b2728d13558c1.png) priority_queue的底层结构是堆 ```cpp #pragma once #include #include #include using namespace std; namespace Boogiepop { template> class priority_queue { public: priority_queue() = default; //迭代器区间初始化 template priority_queue(InputItreator first, InputItreator last) :_con(first, last) { //向下调整堆 for (int i = _con.size() / 2 - 1; i >= 0; --i) { adjust_down(i); } } void push(const T& x) { _con.push_back(x); adjust_up(_con.size() - 1); } void pop() { swap(_con[0],_con[_con.size()-1]); _con.pop_back(); adjust_down(0); } const T& top() const { return _con[0]; } bool empty() const { return _con.empty(); } size_t size() const { return _con.size(); } private: container _con; //向上调整 void adjust_up(int child) { int parent = (child - 1) / 2;//不需要考虑左右孩子 while (parent >= 0) { if (_con[child] > _con[parent]) { swap(_con[child], _con[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } } //向下调整 void adjust_down(int parent) { //选出大孩子 int child = 2 * parent + 1; while (child < _con.size()) { if (child + 1 < _con.size() && _con[child + 1] > _con[child]) { ++child; } if (_con[parent] < _con[child]) { swap(_con[parent], _con[child]); parent = child; child = 2 * parent + 1; } else { break; } } } }; } ``` ## 仿函数(重点!!!) 仿函数是C++为了替代函数指针而存在的(因为函数指针过于复杂)。而且函数指针没法应用于类模板 比如: ```cpp #define _CRT_SECURE_NO_WARNINGS #include using namespace std; //仿函数/函数对象 template class Less { public: bool operator()(const T& a, const T& b) const { return a < b; } }; template class Greater { public: bool operator()(const T& a, const T& b) const { return a > b; } }; int main() { Less lessfunc; cout< using namespace std; //仿函数/函数对象 template class Less { public: bool operator()(const T& a, const T& b) const { return a < b; } }; template class Greater { public: bool operator()(const T& a, const T& b) const { return a > b; } }; //仿函数改造冒泡排序 template void Bubbersort(T* a, int n,Compare cmp) { int exhange = 0; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (cmp(a[j], a[j + 1])) { exhange = a[j]; a[j] = a[j + 1]; a[j + 1] = exhange; } } if (exhange == 0) { break; } } } int main() { /*Less lessfunc; cout<()); for (auto& i : a) { cout << i << " "; } cout< #include #include using namespace std; namespace Boogiepop { //仿函数/函数对象 template class Less { public: bool operator()(const T& a, const T& b) const { return a < b; } }; template class Greater { public: bool operator()(const T& a, const T& b) const { return a > b; } }; //仿函数改造 template,class Compare=Less> class priority_queue { public: priority_queue() = default; //迭代器区间初始化 template priority_queue(InputItreator first, InputItreator last) :_con(first, last) { //向下调整堆 for (int i = _con.size() / 2 - 1; i >= 0; --i) { adjust_down(i); } } void push(const T& x) { _con.push_back(x); adjust_up(_con.size() - 1); } void pop() { swap(_con[0],_con[_con.size()-1]); _con.pop_back(); adjust_down(0); } const T& top() { return _con[0]; } bool empty() const { return _con.empty(); } size_t size() const { return _con.size(); } private: container _con; //向上调整 void adjust_up(int child) { Compare com; int parent = (child - 1) / 2;//不需要考虑左右孩子 while (child >= 0) { //等同于if (_con[child] < _con[parent]) if (com(_con[parent] , _con[child])) { swap(_con[child], _con[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } } //向下调整 void adjust_down(int parent) { Compare com; //选出大孩子 int child = 2 * parent + 1; while (child < _con.size()) { //等同于if (child + 1 < _con.size() &&_con[child] < _con[child + 1]) if (child + 1 < _con.size() &&com( _con[child ] , _con[child+1])) { ++child; } if (com(_con[parent] , _con[child])) { swap(_con[parent], _con[child]); parent = child; child = 2 * parent + 1; } else { break; } } } }; } ``` 测试: ```cpp #define _CRT_SECURE_NO_WARNINGS #include"priority_queue.h" void test1() { Boogiepop::priority_queue pq; //Boogiepop::priority_queue,greater> pq; pq.push(0); pq.push(6); pq.push(3); pq.push(20); pq.push(1); while (!pq.empty()) { std::cout << pq.top() << " "; pq.pop(); } std::cout << std::endl; } int main() { test1(); return 0; } ``` 上下两个优先级队列pq的结果分别为: ![](https://i-blog.csdnimg.cn/direct/02a2c24c9ceb4ded9dab3ab680de004e.png) ![](https://i-blog.csdnimg.cn/direct/7eaa8ea03b154f0abc048b09b91a7bce.png) 我们在之前类和对象的时候会接触到一些空类(即没有成员变量的类),它的一个用途就是仿函数。 下面再举一个例子:查找第一个偶数 ```cpp #define _CRT_SECURE_NO_WARNINGS #include using namespace std; struct OP1 { bool operator()(int x) const { return x % 2 == 0; } }; int main() { int a[] = { 3,5,2,8,1,9,4,7,6 }; //查找第一个偶数 int n=sizeof(a)/sizeof(a[0]); auto it =find_if(a, a + n, OP1()); cout << *it << endl; //如果没有找到返回最后一个位置 return 0; } ``` 结果为: ![](https://i-blog.csdnimg.cn/direct/a70f3a73d2ec40f2b5d8159156e1169d.png) 本期内容就到这里,喜欢的话点个赞谢谢 封面图自取: ![](https://i-blog.csdnimg.cn/direct/befb8f84f60e4284b921869d202657a1.jpeg)

相关推荐
Maybyy3 分钟前
javaScript中数组常用的函数方法
开发语言·前端·javascript
alicema111132 分钟前
萤石摄像头C++SDK应用实例
开发语言·前端·c++·qt·opencv
爱代码的小黄人33 分钟前
MATLAB中绘制系统零极点图(Pole-Zero Map)的几种方法
开发语言·matlab
伍哥的传说38 分钟前
Vue3 Anime.js超级炫酷的网页动画库详解
开发语言·前端·javascript·vue.js·vue·ecmascript·vue3
The Chosen One98540 分钟前
C++ :vector的介绍和使用
开发语言·c++
一只爱做笔记的码农40 分钟前
【C#】Vscode中C#工程如何引用自编写的dll
开发语言·vscode·c#
楼田莉子40 分钟前
Linux学习之认识Linux的基本指令
linux·运维·服务器·开发语言·学习
Aurora_wmroy1 小时前
算法竞赛备赛——【图论】求最短路径——Floyd算法
数据结构·c++·算法·蓝桥杯·图论
疾跑哥布林升级版1 小时前
网络编程7.17
开发语言·网络
青岛少儿编程-王老师1 小时前
CCF编程能力等级认证GESP—C++1级—20250628
java·开发语言·c++