【C++第13章】Stack、Queue和priority_queue
Stack、Queue和priority_queue介绍🧐
stack、queue和priority_queue都是容器适配器,它们不用自己管理数据,而是让其他容器管理,自己封装转换。
适配器指的是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。
stack和queue不支持迭代器,因为会破坏自身结构。queue也不适配vector,因为结构不匹配。
stack(栈)🧐
stack是一种容器适配器,专门用在LIFO上下文**(后进先出)操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作**。
stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
empty:判空操作。
back:获取尾部元素操作。
push_back:尾部插入元素操作。
pop_back:尾部删除元素操作。
标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器, 默认情况下使用deque。
queue(队列)🧐
队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
empty:检测队列是否为空
size:返回队列中有效元素的个数
front:返回队头元素的引用
back:返回队尾元素的引用
push_back:在队列尾部入队列
pop_front:在队列头部出队列
标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。
deque(双端队列)🧐
stack、queue和priority_queue使用时只需要传类型过去,却不需要传容器,这是因为底层给了容器缺省值——deque(双端队列),deque不是队列,它两端都可以进出,且支持各种功能,与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。
deque并不是真正的连续空间,而是由一段段连续的小空间拼接出来的,实际deque类似于一个动态的二维数组,然后用一个中控数组(本质是指针数组)控制,当数据满了需要尾插或者头插时,新开一个数组就可以解决,仅有中控数组的扩容消耗,且只需要拷贝指针,代价很低。
但如果我们要往中间插入数据,就会有两种不同的选择,整体挪动数据、对单个数组进行扩容,前一种挪动效率慢,但是每个数组大小一样,方便访问数据,后一种效率快一点,但是数组大小不一致,不方便访问数据。
那么deque既然这么好,为什么还会有list和vector容器呢?原因是deque有一个缺陷——不适合遍历,在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,所以在实际使用时,vector和list使用的更多。而deque作为stack和queue的默认容器是因为它俩没有迭代器,且deque扩容效率和内存使用率高,结合了deque的优点而避开了缺点。下面代码是vector和deque的排序速度对比。
```C++ void Test() {deque<int> de;vector<int> v;for (size_t i = 0; i < 100000; i++){auto j = rand();de.push_back(j);v.push_back(j);}int begin1 = clock();sort(v.begin(), v.end());int end1 = clock();cout << "vector:" << end1 - begin1 << endl;int begin2 = clock();sort(de.begin(), de.end());int end2 = clock();cout << "deque:" << end2 - begin2 << endl; }
priority_queue(优先级队列)🧐
priority_queue(优先级队列)也不是传统的队列,它也是容器适配器,可以看做为堆,并且默认是大堆,排出来是降序,如果需要建小堆,就要用到仿函数。
仿函数实际是一个对象,但他能像函数一样去使用,仿函数大多数地方是为了替代函数指针。
比如优先级队列想变小堆,可以写一份greater类,然后传过去就可以了。
与sort不同,优先级队列是模板参数,需要传类型过去,sort是函数参数,需要传对象过去,所以使用sort时需要加个扩容,当成匿名对象传过去。
当我们存入一个对象的指针时可能会出现问题,图中我们写了一个日期的类,并以指针的形式传存储,在多次打印后会发现日期不是最大值。
原因是这里优先级队列存储的是指针,也就是地址,那么它会按照地址比较,而new出来的地址大小关系是随机的,所以会出现这种情况,解决方法是我们自己写一个仿函数,去修正比较方式。
模拟实现🧐
stack🔎
namespace Stack
{template<class T, class Container = deque<T>>class stack{public:void push(const T& x){_con.push_back(x);}void pop(){_con.pop_back();}const T& top(){return _con.back();}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:Container _con;};
};
queue🔎
namespace Queue
{template<class T, class Container = deque<T>>class queue{public:void push(const T& x){_con.push_back(x);}void pop(){_con.pop_front();}const T& front(){return _con.front();}const T& back(){return _con.back();}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:Container _con;};
}
priority_queue🔎
namespace Priority
{template<class T, class Container = vector<T>,class Compare = Less<T>>class priority_queue{public:priority_queue(){}template<class InputIterator>priority_queue(InputIterator first, InputIterator last):_con(first,last){for (size_t i = (_con.size()-2)/2; i >= 0; i--){AdjustDown(i);}}void AdjustUp(int child) //向上调整{Compare com;int parent = (child - 1) / 2;while (child > 0){//less时等价于if(_con[parent] < _con[child])if(com(_con[parent],_con[child])){swap(_con[parent], _con[child]);child = parent;parent = (child - 1) / 2;}else{break;}}}void AdjustDown(int parent) //向下调整{Compare com;int child = parent * 2 + 1;while (child < (int)_con.size()){if (child + 1 < (int)_con.size() && com(_con[child],_con[child + 1])){child++;}if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void push(const T& x){_con.push_back(x);AdjustUp(_con.size() - 1);}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}const T& top(){return _con[0];}bool empty(){return _con.empty();}private:Container _con;};
}
结尾👍
以上便是Stack、Queue和Priority_Queue的全部介绍,如果有疑问或者建议都可以私信笔者交流,大家互相学习,互相进步!🌹