🌈前言🌈
欢迎收看本期【C++杂货铺】,本期内容将讲解C++STL中stack和queue的内容,其中包含了stack , queue,priority_queue是什么,怎么使用以及模拟实现这些容器。
此外,还将将讲解设计模式中的适配器模式,以及STL中stack,queue的底层deque。
📁 stack 介绍和使用
📂 介绍
stack是一种容器适配器,专门用于具有先进后出的上下文环境,只能从容器的一端进行元素的插入和提取操作。
stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层,元素特定容器的尾部被压入和弹出。
stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持一下操作:
● empty : 判空操作。
● back:获取尾部元素操作。
● push_back:尾部插入元素操作。
● pop_back:尾部删除元素操作。
标准容器vector,deque,list均符合这些需求,默认情况下,如果没有stack指定特定的底层容器,默认情况下使用deque。
📂 使用
stack - C++ Reference (cplusplus.com)
#include <iostream>
#include <stack>
using namespace std;
int main()
{
stack<int> st;
st.push(1);
st.push(2);
st.push(3);
while(!st.empty())
{
cout<<st.top()<<" ";
st.pop();
}
cout<<endl;
}
📂 模拟实现
#include<deque>
namespace exercise
{
template<class T, class Con = 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:
Con _c;
};
}
📁 queue 介绍和使用
📂 介绍
队列也是一种容器适配器,专门用于先进先出的操作,其中从容器一端插入元素,另一端提取元素。
队列作为容器适配器实现,容器适配器将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素,元素从队尾入队列,从队头出队列。
底层容器可以是变化尊模板容器类模板之一,也可以是其他专门设计的容器类,该底层容器应该支持一下操作:
● empty : 检测队列是否为空。
● size : 返回队列中有效元素的个数。
● front : 返回队头元素的引用。
● back:返回队尾元素的引用。
● push_back:在队列尾部入队列。
● pop_back:在队列头部出队列。
标准容器类deque和list满足这些要求,默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。
📂 使用
queue - C++ Reference (cplusplus.com)
#include <iostream>
#include <queue>
using namespace std;
int main()
{
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
while(!q.empty())
{
cout<<q.front()<<" ";
q.pop();
}
cout<<endl;
return 0;
}
📂 模拟实现
#include <deque>
#include <list>
namespace exercise
{
template<class T, class Con = deque<T>>
//template<class T, class Con = list<T>>
class queue
{
public:
queue() {}
void push(const T& x) {_c.push_back(x);}
void pop() {_c.pop_front();}
T& back() {return _c.back();}
const T& back()const {return _c.back();}
T& front() {return _c.front();}
const T& front()const {return _c.front();}
size_t size()const {return _c.size();}
bool empty()const {return _c.empty();}
private:
Con _c;
};
}
📁 priority_queue 介绍和使用
📂 介绍
优先队列也是一种容器适配器,根据严格的若排序标准,它的第一个元素总是它所包含元中最大的。
本质上优先队列就是堆,在堆中可以随时插入元素,并且只能检索最大/小元素(优先队列中位于顶部的元素)。
容器适配器将特定容器类封装为底层容器类,priority_queue提供一组特定的成员函数来访问其元素。元素从特定容器的尾部弹出,其称为优先队列的顶部。
底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类,容器应该是可以通过随机访问迭代器访问,支持以下操作:
● empty : 检测容器是否为空。
● size : 返回容器中有效元素的个数。
● front : 返回容器第一个元素的引用。
● push_back:在容器尾部插入元素。
● pop_back:删除容器尾部元素。
标准容器类vector和deque满足这些需求。默认情况下,使用vector作为底层容器类。
需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数来自动完成操作。
📂 使用
#include <iostream>
#include <queue>
using namespace std;
int main()
{
//默认建大堆
//等价于 priority_queue<int,vector<int>,less<int>>
priority_queue<int> p1;
//传递仿函数 , 建小堆
priority_queue<int,vector<int>,greater<int>> p2;
p1.push(1);
p1.push(2);
p1.push(3);
p2.push(3);
p2.push(2);
p2.push(1);
//p1 : 3,2,1
//p2 : 1,2,3
}
📂 仿函数
仿函数(Functor)是一种行为类似于函数的对象。在C++中,仿函数可以被当作函数使用,可以执行函数调用操作,并且可以具有自己的状态。
通常情况下,仿函数是一个类,它重载了函数调用运算符operator()。通过重载该运算符,仿函数可以像函数一样被调用,接受参数并返回结果。重载了operator[]的类,就是仿函数。
仿函数的对象可以像函数一样调用。
仿函数可以控制比较逻辑,控制如何比较。
📂 模拟实现
namespace exercise
{
template <class T>
struct less
{
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
template<class T>
struct greater
{
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
template<class T,class Container = vector<T>,class Compare = less<T>>
class priority_queue
{
public:
void AdjustUp(int child)
{
Compare com;
int parent = (child - 1) / 2;
while (child > 0)
{
//if (_con[child] > _con[parent])
if(com(_con[parent],_con[child]))
{
std::swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void push(const T& val)
{
_con.push_back(val);
AdjustUp(_con.size() - 1);
}
void AdjustDown(int parent)
{
Compare com;
int child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
{
child++;
}
if (com(_con[parent], _con[child]))
{
std::swap(_con[child],_con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void pop()
{
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
const T& top()
{
return _con[0];
}
private:
Container _con;
};
}
📁 容器适配器
📂 概念
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。
📂 STL库中stack和queue的底层结构
虽然stack和queue中也可以存放元素,但在STL中并没有讲其划分为容器行列,而是将其称为容器适配器,这是因为stack和queue只是堆其他容器的接口进行包装,STL中stack和queue默认使用deque。
📂 deque介绍
deqeue(双端队列):是一种双开口“连续空间的数据结构”。双开口的含义是:可以再头尾两端进行插入和删除操作,且时间复杂度为O(1);和vector比较,头插效率高,不需要搬移元素;和list比,空间利用率较高。
deque并不是真正连续的空间,而是由一端连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,底层结构如下:
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在deque的迭代器上,因此deque的迭代器的设计比较复杂。如图所示:
deque的缺陷:
和vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此效率比vector高。
和list比较,其底层空间时连续的,空间利用率特别高,不需要存储额外字段。
但是deque有一个致命缺陷,不适合遍历,因为在遍历时,deque的迭代器要频繁的区间所是否移动到某段小空间的边界,导致效率低下,在序列式场合中,肯呢个需要经常遍历,因此在实际中,需要线性结构时,大多数有限考虑vector和list,deque的应用不多,目前的一个应用就是.STL用其为stack和queue的底层容器类。
为什么选择人deque作为stack和queue的底层默认容器:
stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可 以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有 push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和 queue默认选择deque作为其底层容器,主要是因为:
1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长 时,deque不仅效率高,而且内存使用率高。
结合了deque的优点,而完美的避开了其缺陷。
📁 总结
以上,就是本期【C++杂货铺】stack和queue的主要内容了,包含了它stack,queue,priority_queue的介绍和使用方法,以及他们模拟实现他们的底层。
此外,还介绍了仿函数是什么,就是一个冲在了operator()的类,使用它的对象就像使用函数一样。
如果感觉本期内容对你有帮助,欢迎点赞,收藏,关注。Thanks♪(・ω・)ノ