前言
在上篇讲解了list的模拟实现,遇到了
迭代器失效、拷贝构造、指针指向逻辑等问题。在
string、vector、list中运用过许多函数接口,而这篇会跳过函数接口的使用,直接向下解决栈和队列的模拟实现
stack与queue
一、deque的简单介绍
1.1deque的原理介绍
deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其"整体连续"以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:

那deque是如何借助其迭代器维护其假想连续的结构呢?

1.2deque的缺陷
对比 vector:
- 头插 / 头删不需要移动元素,效率更高
- 扩容时不需要移动大量元素,效率更高
对比 list:
底层是分段连续空间,空间利用率更高
缺陷:不适合频繁遍历场景:遍历时 deque 的迭代器需要频繁检测是否到达某段小空间的边界,导致遍历效率低于 vector/list
序列式场景中通常需要频繁遍历,因此实际开发中,需要线性结构时优先选择 vector 和 list,deque 的直接应用场景较少;但 STL 选择 deque 作为 stack 和 queue 的底层默认容器,核心原因如下:
1.3deque作为stack和queue的底层默认容器
- stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
- 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。
二、stack和queue的整体框架
stack、queue均基于容器适配器 实现(默认底层容器为 deque),核心区别是:stack(栈)遵循先进后出(LIFO)规则,queue(队列)遵循先进先出规则。
cpp
// Stack.h
#pragma once
#include<deque>
namespace yuuki
{
// 容器适配器:默认以deque为底层容器实现stack
template<class T, class Container = deque<T>>
class stack
{
public:
// 插入接口(支持const引用和移动语义)
void push(const T& x)
{
_con.push_back(x);
}
void push(T&& x)
{
_con.push_back(std::move(x));
}
// 删除接口
void pop()
{
_con.pop_back();
}
// 返回栈顶元素(const/非const版本)
const T& top() const
{
return _con.back();
}
T& top()
{
return _con.back();
}
// 有效元素个数
size_t size() const
{
return _con.size();
}
// 判空
bool empty() const
{
return _con.empty();
}
private:
Container _con; // 底层容器(默认deque)
};
}
cpp
// queue.h
#pragma once
#include<deque>
namespace yuuki
{
// 容器适配器:默认以deque为底层容器实现queue
template<class T, class Container = deque<T>>
class queue
{
public:
// 插入接口(支持const引用和移动语义)
void push(const T& x)
{
_con.push_back(x);
}
void push(T&& x)
{
_con.push_back(std::move(x));
}
// 删除接口
void pop()
{
_con.pop_front();
}
// 返回队头元素(const/非const版本)
const T& front() const
{
return _con.front();
}
T& front()
{
return _con.front();
}
// 返回队尾元素(const/非const版本)
const T& back() const
{
return _con.back();
}
T& back()
{
return _con.back();
}
// 有效元素个数
size_t size() const
{
return _con.size();
}
// 判空
bool empty() const
{
return _con.empty();
}
private:
Container _con; // 底层容器(默认deque)
};
}
三、priority_queu的介绍和使用
3.1priority_queu的介绍
- 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
- 底层逻辑等价于 "堆":可随时插入元素,仅能访问 / 删除堆顶(优先队列顶部)元素;
- 优先队列基于容器适配器实现,封装特定容器作为底层存储,通过堆算法维护堆结构;
- 底层容器需支持随机访问迭代器,并提供以下接口:
empty():检测容器是否为空size():返回容器中有效元素个数front():返回容器中第一个元素的引用push_back():在容器尾部插入元素pop_back():删除容器尾部元素
- 标准容器
vector/deque满足上述需求,默认使用vector作为底层容器;- 容器适配器自动调用
make_heap、push_heap、pop_heap算法维护堆结构。
3.2priority_queu的使用
优先级队列默认使用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是大堆
cpp
#include <vector>
#include <queue>
#include <functional> // greater算法头文件
#include <iostream>
using namespace std;
void Test()
{
// 默认大堆:底层按小于号比较,堆顶为最大值
vector<int> v{3,6,3,8,0,9,5,1};
priority_queue<int> q1;
for(auto& e : v)
{
q1.push(e);
}
cout << q1.top() << endl; // 输出:9
// 小堆:指定底层容器为vector,比较规则为greater<int>
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl; // 输出:0
}
- 存储自定义类型时,需重载
<(大堆)或>(小堆)运算符。
cpp
#include <vector>
#include <queue>
#include <functional>
#include <iostream>
using namespace std;
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; // 输出:2018-10-30
// 小堆:依赖>运算符重载
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; // 输出:2018-10-28
}
3.3priority_queu的模拟实现
cpp
#pragma once
#include<vector>
#include<utility> // std::swap
#include<iostream>
using namespace std;
// 大堆比较规则(默认)
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 yuuki
{
// 优先队列(堆)模拟实现:默认大堆,底层容器为vector
template<class T, class Container = vector<T>, class Compare = Less<T>>
class priority_queue
{
public:
// 默认构造
priority_queue() = default;
// 迭代器区间构造(建堆)
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
_con.push_back(*first);
++first;
}
// 从最后一个非叶子节点开始向下调整,构建堆
for (int i = (_con.size() - 2) / 2; i >= 0; --i)
{
AdjustDown(i);
}
}
// 向上调整:维护堆结构(插入元素后调用)
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
// 根据比较规则调整(大堆/小堆)
if (com(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
// 插入元素
void push(const T& x)
{
_con.push_back(x);
AdjustUp(_con.size() - 1);
}
// 向下调整:维护堆结构(删除堆顶后调用)
void AdjustDown(size_t parent)
{
size_t 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]))
{
swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
// 删除堆顶元素
void pop()
{
if (empty())
{
cout << "priority_queue is empty!" << endl;
return;
}
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
// 返回堆顶元素(const/非const版本)
const T& top() const
{
return _con.front();
}
T& top()
{
return _con.front();
}
// 有效元素个数
size_t size() const
{
return _con.size();
}
// 判空
bool empty() const
{
return _con.empty();
}
private:
Container _con; // 底层容器
Compare com; // 比较规则对象
};
}