欢迎来到Cefler的博客😁
🕌博客主页:那个传说中的man的主页
🏠个人专栏:题目解析
🌎推荐文章:题目大解析3
目录
👉🏻Stack Constructor
👉🏻Stack Member functions
👉🏻Queue constructor
👉🏻Queue Member functions
👉🏻Stack和Queue的模拟实现(模板实现)
#pragma once
#include <iostream>
#include <list>
#include <deque>
using namespace std;
namespace Space
{
template <class T, class Container = deque<T> >
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()//尾删
{
_con.pop_back();
}
T top()
{
return _con.back();
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
};
template <class T >
class queque
{
public:
void push(const T& x)
{
_lt.push_back(x);
}
void pop()//头删
{
_lt.pop_front();
}
T& front()
{
return _lt.front();
}
T& back()
{
return _lt.back();
}
bool empty()
{
return _lt.empty();
}
size_t size()
{
return _lt.size();
}
private:
list<T> _lt;
};
}
为什么deque可以作为栈和队列的适配容器?
下面引入deque。
👉🏻deque
C++中的deque
(双端队列)是一种双向开口的动态数组,它可以在两端进行高效的插入和删除操作。deque
具有以下的优点和缺点:
优点:
-
高效的插入和删除操作:
deque
在首尾两端的插入和删除操作都非常高效,时间复杂度为O(1)。这使得它适用于需要频繁在两端插入或删除元素的场景。 -
随机访问:与
vector
类似,deque
也支持随机访问,即可以通过索引快速访问任意位置的元素。由于其内部实现采用了分段连续存储的结构,使得索引访问的性能接近于数组。 -
动态扩展:
deque
可以根据需要动态地增加容量,而且当容量不够时,它的扩展方式比vector
更加高效。deque
内部由多个块组成,每个块的大小可以根据需求进行调整。
缺点:
-
内存消耗:相比较于
vector
,deque
在内存消耗上稍微更多。因为它需要维护多个块,并且每个块都需要额外的指针来链接。 -
迭代器失效:在对
deque
进行插入或删除操作后,迭代器可能会失效。这是因为deque
为了保持两端的高效操作,可能会在中间插入或删除元素,导致原来的迭代器失去指向有效元素的能力。需要注意及时更新迭代器。
综上所述,deque
在双端的插入和删除操作上具有较高的效率,并支持随机访问,适用于需要频繁操作头尾的场景。然而,需要注意内存消耗和迭代器失效的问题。根据实际需求选择合适的容器是很重要的。
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维
数组,其底层结构如下图所示
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落
在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:
为什么选择deque作为stack和queue的底层默认容器?
stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可
以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有
push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和
queue默认选择deque作为其底层容器,主要是因为:
- stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
- 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长
时,deque不仅效率高,而且内存使用率高。
结合了deque的优点,而完美的避开了其缺陷。
👉🏻优先级队列——priority_queue
C++ 的 priority_queue 是一个数据结构,它是一个优先级队列,可以维护一组元素并且每次取出的都是当前最大(或最小)的元素。priority_queue 内部实现方式是使用堆 (heap),一种特殊的树形数据结构,具有以下特性:
- 堆是一个完全二叉树(fully binary tree),即除了最后一层之外,其他的层必须有节点,在最后一层上,所有的节点都集中在左边;
- 堆分为最大堆和最小堆两种类型,其中最大堆的根节点的值最大,最小堆的节点的值最小,priority_queue 默认是最大堆;
- 任何一个节点的值都大于其左子树和右子树的值,这条性质被称为堆属性。
在 C++ 中,priority_queue 是一个模板类,定义在 queue 头文件中。以下是一些 priority_queue 常用操作:
- push(x) :将元素 x 加入到队列中;
- pop() :将队列中最大的元素弹出;
- top() :获取队列中最大的元素,但不弹出;
- empty() :判断队列是否为空;
- size() :返回队列内元素的个数。
优先队列的元素可以是基本数据类型和结构体类型,但是,在存储复杂对象时,需要自定义比较器,用来告诉程序如何比较这些对象的大小。
以下是一个示例,演示了如何使用 priority_queue 实现获取前k个大的元素:
#include <bits/stdc++.h>
using namespace std;
int main() {
int k = 5;
vector<int> nums = {2, 7, 9, 1, 5, 8, 3, 6, 10, 4};
// 定义一个最大堆
priority_queue<int, vector<int>, less<int>> q;//less其实不用写,因为默认就是大堆
// 将前K个元素加入到堆中
for (int i = 0; i < k; ++i) {
q.push(nums[i]);
}
// 遍历剩下的元素
for (int i = k; i < nums.size(); ++i) {
if (q.top() > nums[i]) {
q.pop();
q.push(nums[i]);
}
}
// 输出前k个大的元素
while (!q.empty()) {
cout << q.top() << " ";
q.pop();
}
return 0;
}
运行结果为:10 9 8 7 6
如果创建小堆,将第三个模板参数换成greater比较方式
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
priority_queue模拟实现
code.h:
#pragma once
#include <iostream>
#include <vector>
using namespace std;
namespace Space
{
template <class T>
class Greater
{
public:
bool operator()(int a, int b)//仿函数,运算符重载();意义:替代函数指针
{
return a > b;
}
};
template <class T,class Container = vector<T>,class Compare = less<int>>//less如果改为Greatet就是建小堆了
class priority_queue
{
public:
priority_queue()
{
}
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
:_con(first, last)
{
//向下调整建堆
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)//从尾部的第一个父节点开始
{
adjust_down(i);
}
}
void adjust_up(int child)
{
int parent = (child - 1) / 2;
while (child)
{
Compare com;
if(com(_con[parent] , _con[child]))
/*if ( _con[parent]< _con[child])*/
{
swap(_con[child], _con[parent]);
//更新一下child和parent的位置
child = parent;
parent = (parent - 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() && com(_con[child],_con[child + 1]) )
{
child++;
}
if (com(_con[parent] , _con[child]))
{
swap(_con[child], _con[parent]);
parent = child;
child = 2 * parent + 1;
}
else
break;
}
}
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);
}
T top()
{
return _con[0];
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
};
void Priority_test1()
{
int k = 5;
vector<int> nums = { 2, 7, 9, 1, 5, 8, 3, 6, 10, 4 };
Space::priority_queue<int> pq(nums.begin(),nums.end());
/*for (int i = 0; i < nums.size(); i++)
{
pq.push(nums[i]);
}*/
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
}
}
test result;
👉🏻LeetCode题
最小栈
class MinStack {
public:
MinStack()
{
//不需要写构造函数
//自定义类型会去调用它自己的默认构造函数
}
void push(int val) {
_st.push(val);
if(_minst.empty()||val<=_minst.top())
{
_minst.push(val);
}
}
void pop() {
//如果_st的栈顶等于_minst的栈顶,_minst也要出栈
if(_st.top()==_minst.top())
{
_minst.pop();
}
_st.pop();
}
int top() {
return _st.top();
}
int getMin() {
return _minst.top();
}
stack<int> _st;
stack<int> _minst;
};
那么如果在st中插入很多重复的数据,我们还要再向_minst中插入吗?
其实我们这里可以构建一个结构体
struct CountVal
{
int _val;
int count = 0;
};
我们插入一个结构体,遇到相同的++count 即可
栈的压入、弹出序列
原题链接:栈的压入、弹出序列
MyCode:
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pushV int整型vector
* @param popV int整型vector
* @return bool布尔型
*/
bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
stack<int> st;
size_t popV_sz = popV.size();
size_t pushV_sz = pushV.size();
int i = 0, j = 0;
int flag = 0;
for(i=0,j =0;j<pushV_sz;j++)
{
//先从栈里面先找
while(!st.empty())
{
if(st.top()==popV[i])
{
i++;
flag = 1;//找到了
//找到后记得pop掉
st.pop();
}
else
{
break;
}
}
if(popV[i] == pushV[j])
{
i++;
flag = 1;//找到了
}
else
{
st.push(pushV[j]);
}
}
if(!flag)
return false;//如果结束都找不到
//接下来进入出栈匹配环节
while(!st.empty())
{
if(popV[i++]!=st.top())
return false;
st.pop();
}
return true;
}
};
思路思想:
其实后续发现无需flag,只要最后出栈环节匹配不上也不对。
所以优化掉flag后
class Solution {
public:
bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
stack<int> st;
size_t popV_sz = popV.size();
size_t pushV_sz = pushV.size();
int i = 0, j = 0;
for(i=0,j =0;j<pushV_sz;j++)
{
//先从栈里面先找
while(!st.empty())
{
if(st.top()==popV[i])
{
i++;
//找到后记得pop掉
st.pop();
}
else
{
break;
}
}
if(popV[i] == pushV[j])
{
i++;
}
else
{
st.push(pushV[j]);
}
}
//接下来进入出栈匹配环节
while(!st.empty())
{
if(popV[i++]!=st.top())
return false;
st.pop();
}
return true;
}
};
other version:
bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
stack<int> st;
int 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();
}
};
数组中的第K个最大元素(优先级队列秒杀)
原题链接:数组中的第K个最大元素
mycode:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int> pq(nums.begin(),nums.end());
while(--k)
{
pq.pop();
}
return pq.top();
}
};
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长