适配器:
虽然 stack 和 queue 中也可以存放元素,但在 STL 中并没有将其划分在容器的行列,而是将其称为 容器适配器 ,这是因为 stack 和队列只是对其他容器的接口进行了包装, STL 中 stack 和 queue 默认使用deque
换种生活上的理解:
我国家用电的电压标准一般是220V,现在大家既然看到这篇博客了,那应该自己也是有一条充电器了。充电器,顾名思义,就是用来给电池充电的设备,它通常包括一个电源适配器和一根充电线。而电源适配器,则是充电器的一个重要组成部分,它的主要功能是将交流电(AC)转换成直流电(DC),并调整电压和电流以满足不同设备的充电需求。 这里电源适配器就是一种适配器,防止了220V的电压与我们接触,保证了我们的人身安全
STL
中
stack
和
queue
默认使用deque作为适配器(Container):
可以简单理解为:站和队列就是一个包装(一个比较特别的包装)
stack:

queue:priority_queue:
stack与queue:(简单回忆)
栈:一种后进先出的数据结构:(表层的印象理解:)
队列:一种先进先出的数据结构:(表层的印象理解:)
deque的简单介绍:
原理介绍:
deque(
双端队列
)
:是一种双开口的
"
连续
"
空间的数据结构
,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1)
,与
vector
比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。

deque其实就是vector和list的缝合怪
vector | list |
优点: | 优点: |
尾插尾删效率不错,支持高效的下标随机访问 | 按需申请释放空间,不需要扩容 |
物理空间连续,所以高速缓存利用率高 | 任意位置插入删除 |
缺点: | 缺点: |
空间需要扩容,扩容有一些代价(效率和空间的浪费) | 不支持下标随机访问 |
头部和中间位置插入和删除效率低 |
deque
并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际
deque
类似于一个
动态的二维数组
,其底层结构如下图所示:

看STL_deque原码理解更佳:
总结:
- deque头插尾插效率很高,更甚于vector和list
- deque下标随机访问也还不错,但是相比于vector,vector更胜一筹
- deque的中间插入删除效率很低
这也是为什么STL中stack和queue的适配器是给deque作为缺省值,因为stack和queue的主要实现功能还是出现在头部与尾部,deque在头尾的操作有着比vector和list更加高效的能力,因此,deque作为stack和queue的Cantainer是更适合不过的了,但是在其他情况就该考虑是否vector/list会不会更好呢?
STL中stack的实现:
丰富请参考(点击进入)
功能介绍:
函数声明 | 接口说明 |
---|---|
stack()
|
构造空的栈
|
empty()
|
检测
stack
是否为空
|
size()
|
返回
stack
中元素的个数
|
top()
|
返回栈顶元素的引用
|
push()
|
将元素
val
压入
stack
中
|
pop()
|
将
stack
中尾部的元素弹出
|
底层实现:
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
using namespace std;
//template<class T>
//class stack
//{
//private:
// T* _a;
// size_t _top;
// size_t _capacity;
//};
namespace home
{
template<class T,class Container = deque<T>>
class stack
{
public:
//Container是一个自定义类型,会自动调用其构造函数,我们无需在此实现
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
const T& top() const
{
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;//容器的适配转换出的stack
};
}
STL中queue的实现:
丰富请参考(点击进入)
功能介绍:
函数声明 | 接口说明 |
---|---|
queue()
|
构造空的队列
|
empty()
|
检测队列是否为空,是返回
true
,否则返回
false
|
size()
|
返回队列中有效元素的个数
|
front()
|
返回队头元素的引用
|
back()
|
返回队尾元素的引用
|
push()
|
在队尾将元素
val
入队列
|
pop()
|
将队头元素出队列
|
底层实现:
#pragma once
#include<iostream>
#include<deque>
#include<list>
using namespace std;
namespace home
{
template<class T, class Container = deque<T>>
class queue
{
public:
//Container是一个自定义类型,会自动调用其构造函数,我们无需在此实现
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
const T& front() const
{
return _con.front();
}
const T& back() const
{
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;//容器的适配转换出的stack
};
}
STL中priority_queue的实现:
丰富请参考(点击进入)
功能介绍:
1.
优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的;
2.
此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素
(
优先队列中位于顶部的元素);
3.
优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,
queue提供一组特定的成员函数来访问其元素。元素从特定容器的“
尾部
”
弹出,其称为优先队列的顶部;
4.
底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
- empty():检测容器是否为空
- size():返回容器中有效元素个数
- front():返回容器中第一个元素的引用
- push_back():在容器尾部插入元素
- pop_back():删除容器尾部元素
5.
标准容器类
vector
和
deque
满足这些需求。默认情况下,如果没有为特定的
priority_queue类实例化指定容器类,则使用vector;
6.
需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap
、
push_heap
和
pop_heap
来自动完成此操作;
优先级队列默认使用 vector 作为其底层存储数据的容器,在 vector 上又使用了堆算法将 vector 中 元素构造成堆的结构,因此 priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue 。注意:默认情况下 priority_queue 是大堆
函数声明 | 接口说明 |
---|---|
priority_queue()/priority_queue(first, last)
|
构造一个空的优先级队列
|
empty()
|
检测优先级队列是否为空,是返回
true
,否则返回
false
|
top()
|
返回优先级队列中最大
(
最小元素
)
,即堆顶元
素
|
push(x)
|
在优先级队列中插入元素
x
|
pop()
|
删除优先级队列中最大
(
最小
)
元素,即堆顶元
素
|
在默认情况下priority_queue是大堆,我们可以利用仿函数进行对priority_queue修改成小堆:
仿函数在冒泡排序的使用(参照)(本质一样):(下面有相关的实现代码)
在相关文档中,就体现了仿函数:
仿函数用于回调,又是一个类,可以使用模板参数
底层实现:
#pragma once
#include<vector>
//仿函数,本质是一个类,这个类重载operator(),他的对象可以像仿函数一样来使用
template<class T>
class Less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
//仿函数大多是没有成员变量的
};
template<class T>
class Greater
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
//仿函数用于回调,又是一个类,可以使用模板参数
//注意:常常用匿名对象
namespace home
{
template<class T, class Container = std::vector<T>, class Compare = Less<T>>
class priority_stack
{
//优先级队列其实就是默认按照大堆来top与pop
public:
void AdjustUp(int child)//向上调整
{
Compare com;
int parent = (child - 1) / 2;
while (child > 0)
{
//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)//向下调整
{
//假设法:先设左孩子小
size_t child = parent * 2 + 1;
Compare com;
while (child < _con.size())//child>=n说明孩子不存在,调整到叶子了
{
//孩子的正真调整
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
{
++child;
}
//if (_con[parent] < _con[child])
if (com(_con[parent], _con[child]))
{
swap(_con[parent], _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];
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
}