容器适配器
概念
- 设计模式:设计模式是一套被反复使用的、多数人知晓的、经过分类编目的代码设计经验总结,代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用,是软件开发人员在软件开发过程中面临的一般问题的解决方案,它主要优点有可重用代码、提高代码的可扩展性以及可维护性。
- 适配器:适配器是一种设计模式,该种模式是将一个类的接口转换成客户希望的另外一个接口;简单来说就是对于用户需要的接口,我们使用其他类来进行封装即可,不需要重新来实现用户所需接口,但是对于这个其他类也是有一定的要求的,只有满足所需的要求,才能成为被用来封装的类;
deque
双端队列
概念
deque
(双端队列):是一种双开口的 “连续” 空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为 O(1),与vector
比较,头插效率高,不需要搬移元素;与list
比较,空间利用率比较高。- 底层实现:
deque
并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque
类似于一个动态的二维数组,只有这种结构才能做到首尾的操作都是 O(1) 时间复杂度,其底层结构如下图所示:
- 迭代器:双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其整体连续以及随机访问的假象,这个重任就落在了
deque
的迭代器身上,因此deque
的迭代器设计就比较复杂,由于以后对于双端队列的使用仅在于,为了实现其它接口而被进行封装的类,这里就不做过多研究,有兴趣的小伙伴可以上网查查看;
优缺点
- 优点:
- 与
vector
比较,deque
的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是比vector
高的; - 与
list
比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段;
- 缺点:不适合遍历,因为在遍历时,
deque
的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector
和list
,deque
的应用并不多;
应用场景
stack
和queue
的底层实现靠的就是deque
,虽然可以使用其他的容器,例如vector
或者list
,但是使用deque
更优,理由如下:
- stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
- 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。
stack
介绍
stack
是一种容器适配器,专门在具有后进先出操作的上下文环境中被使用,只能从容器的一端进行元素的插入、获取和删除操作;stack
是作为容器适配器被实现的,容器适配器是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,元素在容器的尾部 (即栈顶) 被压入和弹出;stack
的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
empty
:判空操作;size
:获取栈中有效元素个数;back
:获取尾部元素操作;push_back
:尾部插入元素操作;pop_back
:尾部删除元素操作;
- 标准容器
vector
、deque
、list
均符合这些需求,一般情况下,如果没有为stack
指定特定的底层容器,默认情使用deque
;
接口
template <class T, class Container = deque<T>> class stack;
:第一个模板参数是栈中所存放元素的类型,第二个模板参数是实现stack
容器适配器的底层类,如果没有指定则默认使用deque
,如果要指定则需要满足上面介绍中的接口要求;stack<typename> name
:创建一个 name 名字的空栈;bool empty() const;
:检测栈是否为空;size_type size() const;
:获取栈中的元素个数;val_type& top();
:返回栈顶元素的引用,返回值可读可写;const val_type& top() const;
:返回栈顶元素的const
引用,返回值只能读,不可写;void push (const value_type& val);
:将元素 val 压入栈中;- 函数模板:内置类型元素直接压入栈,自定义类型元素在压入栈的同时对元素调用构造函数,使用时直接在括号中写入构造函数所需的参数即可;
template <class... Args>
void emplace (Args&&... args);
void pop();
:栈顶元素出栈;void swap (stack& x)
:成员交换函数,交换两个stack
对象的内容;- 外部交换函数,交换两个
stack
对象的内容;
template <class T, class Container>
void swap (stack<T,Container>& x, stack<T,Container>& y)
queue
介绍
- 队列是一种容器适配器,专门用于在 FIFO 上下文 (先进先出) 中操作,其中从容器一端插入元素,另一端提取元素;
- 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,
queue
提供一组特定的成员函数来访问其元素,元素从队尾入队列,从队头出队列; - 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类,该底层容器应至少支持以下操作:
empty
:检测队列是否为空;size
:返回队列中有效元素的个数;front
:返回队头元素的引用;back
:返回队尾元素的引用;push_back
:在队列尾部入队列;pop_front
:在队列头部出队列;
- 标准容器类
deque
和list
满足了这些要求,一般情况下,如果没有为queue
实例化指定容器类,则默认使用标准容器deque
;
接口
template <class T, class Container = deque<T>> class queue;
:第一个模板参数是队列中所存放元素的类型,第二个模板参数是实现queue
容器适配器的底层类,如果没有指定则默认使用deque
,如果要指定则需要满足上面介绍中的接口要求;queue<typename> name
:创建一个 name 名字的空队列;bool empty() const;
:检测队列是否为空;size_type size() const;
:获取队列中的元素个数;value_type& front();
:返回队头元素的引用,返回值可读可写;const value_type& front() const;
:返回队头元素的const
引用,返回值只能读,不可写;value_type& back();
:返回队尾顶元素的引用,返回值可读可写;const value_type& back() const;
:返回队尾顶元素的const
引用,返回值只能读,不可写;void push (const value_type& val);
:将元素 val 从队尾入队;- 函数模板:内置类型元素直接入队,自定义类型元素在入队的同时对元素调用构造函数,使用时直接在括号中写入构造函数所需的参数即可;
template <class... Args>
void emplace (Args&&... args);
void pop();
:队头元素出队;void swap (queue& x)
:成员交换函数,交换两个queue
对象的内容;- 外部交换函数,交换两个
queue
对象的内容;
template <class T, class Container>
void swap (queue<T,Container>& x, queue<T,Container>& y)
priority_queue
介绍
- 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的;
- 该结构在底层就是堆,可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素);
- 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,
queue
提供一组特定的成员函数来访问其元素,元素从特定容器的尾部弹出,其称为优先队列的顶部; - 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类,容器应该可以通过随机访问迭代器访问,并支持以下操作:
empty()
:检测容器是否为空;size()
:返回容器中有效元素个数;front()
:返回容器中第一个元素的引用;push_back()
:在容器尾部插入元素;pop_back()
:删除容器尾部元素;
- 标准容器类
vector
和deque
满足这些需求,一般情况下,如果没有为特定的priority_queue
类实例化指定容器类,则使用vector
; - 需要支持随机访问迭代器,以便始终在内部保持堆结构,容器适配器通过在需要时自动调用算法函数
make_heap
、push_heap
和pop_heap
来自动完成此操作;
接口
- 类模板:第一个模板参数是所存放元素的类型,第二个模板参数是实例化优先队列时指定的容器类,第三个模板参数为优先级队列是创建大堆还是小堆,默认参数为创建大堆;
template <class T, class Container = vector<T>, class Compare = less<typename Container::value_type>> class priority_queue;
priority_queue<typename, 实例化指定容器, 指定大堆还是小堆> name
:创建一个名为 name 的空优先级队列,第二个参数默认使用vector
来实例化,可以自己指定,但是要满足介绍中的要求,第三个参数默认为建大堆,如果指定为greater<typename>
则是创建小堆;priority_queue<typename, 实例化指定容器, 指定大堆还是小堆> name(InputIterator first, InputIterator last)
:创建一个名为 name 的优先级队列,并用指定迭代区间的内容进行初始化,第二个参数默认使用vector
来实例化,可以自己指定,但是要满足介绍中的要求,第三个参数默认为建大堆,如果指定为greater<typename>
则是创建小堆;bool empty() const;
:检测优先队列是否为空;size_type size() const;
:获取优先队列中的元素个数;val_type& top();
:返回优先队列顶部元素的引用,返回值可读可写;const val_type& top() const;
:返回优先队列顶部元素的const
引用,返回值只能读,不可写;void push (const value_type& val);
:在优先级队列中插入元素 val;- 函数模板:内置类型元素直接插入优先队列,自定义类型元素在插入优先队列的同时对元素调用构造函数,使用时直接在括号中写入构造函数所需的参数即可;
template <class... Args>
void emplace (Args&&... args);
void pop();
:优先队列顶部元素出栈;void swap (priority_queue& x)
:成员交换函数,交换两个priority_queue
对象的内容;- 外部交换函数,交换两个
priority_queue
对象的内容;
template <class T, class Container, class Compare>
void swap (priority_queue<T,Container,Compare>&x,priority_queue<T,Container,Compare>&y);
注意事项
- 默认情况下,
priority_queue
是大堆;
#include <vector>
#include <queue>
#include <functional>
void TestPriorityQueue(){
vector<int> v{3,2,7,6,0,4,1,9,8,5};
priority_queue<int> q1;
for (auto& e : v)
q1.push(e);
cout << q1.top() << endl;
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl;
}
- 如果在
priority_queue
中放的是自定义类型数据的话,那么用户需要在自定义类型中提供>
或者<
的重载,否则在元素插入后创建堆时,无法进行比较操作;
class Date{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
friend ostream& operator<<(ostream& _cout, const Date& d){
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
void TestPriorityQueue(){
priority_queue<Date> q1;
q1.push(Date(2018, 10, 29));
q1.push(Date(2018, 10, 28));
q1.push(Date(2018, 10, 30));
cout << q1.top() << endl;
priority_queue<Date, vector<Date>, greater<Date>> q2;
q2.push(Date(2018, 10, 29));
q2.push(Date(2018, 10, 28));
q2.push(Date(2018, 10, 30));
cout << q2.top() << endl;
}
模拟实现