
🔍 文档介绍:stack - C++ Reference
通过观察文档我们不难发现,接口相较于之前的 string、vector 和 list 少了很多。它甚至连拷贝构造和析构都没有自己实现,然而这些都得益于容器适配器的使用。
不难发现, stack、queue 也没有迭代器,这也不难理解,因为栈和丢列本来不是用来遍历的,这样子的话只会破坏他们的原则。
函数说明 | 接口说明 |
|---|---|
stack() | 构造空的栈 |
empty() | 检测 stack 是否为空 |
size() | 返回 stack 中元素的个数 |
top() | 返回栈顶元素的引用 |
push() | 将元素 val 压入 stack 中 |
pop() | 将 stack 中尾部的元素弹出 |
测试代码:
#include <iostream>
#include <stack>
using namespace std;
void test_stack() {
/* 创建一个存储整型的栈 */
stack<int> st;
/* 入栈 */
st.push(1);
st.push(2);
st.push(3);
st.push(4);
/* 打印栈 */
while (!st.empty()) { // 如果栈不为空则进入循环
cout << st.top() << " "; // 打印栈顶元素
st.pop(); // 出栈
}
cout << endl;
}🚩 运行结果:4 3 2 1队列只允许在一端进行插入数据操作,在另一端进行删除数据操作 的特殊线性表。
入队列,进行插入操作的一端称为 队尾。出队列,进行删除操作的一端称为 队头。
队列中的元素遵循先进先出的原则,即 FIFO 原则(First In First Out)。
🔍 文档介绍:queue - C++ Reference
函数声明 | 接口说明 |
|---|---|
queue() | 构造空的队列 |
empty() | 检测队列是否尾空,是返回 ture,否则返回 false |
size() | 返回队列中有效元素的个数 |
front() | 返回队头元素的引用 |
back() | 返回队尾元素的引用 |
push() | 在对尾将元素 val 入队列 |
pop() | 将队头元素出队列 |
💬 代码演示: queue
#include <iostream>
#include <queue>
using namespace std;
void test_queue() {
/* 创建一个存储整型的队列 */
queue<int> Q;
/* 入队 */
Q.push(1);
Q.push(2);
Q.push(3);
Q.push(4);
/* 打印队列 */
while (!Q.empty()) { // 如果队列不为空则进入循环
cout << Q.front() << " "; // 打印队头元素
Q.pop(); // 出队
}
cout << endl;
}🚩 运行结果:4 3 2 1🔍 文档介绍:priority_queue - C++ Reference
从上面的概念可以看出,优先级队列就是一个堆,而且默认是个大堆!
优先级队列默认使用 vector 作为其底层存储数据的容器,
在 vector 上又使用了堆算法将 vector 中元素构造成堆的结构,因为 priority_queue 就是堆。
所有需要用到堆的地方,都可以考虑使用 priority_queue。
优先级队列默认大的优先级高,传的是 less 仿函数,底层是一个大堆;
如果想控制小的优先级高,需手动传 greater 仿函数,其底层是一个小堆。
可能是因为大堆是默认从大到小递减,所以才讲仿函数名称设为 less 哈哈。
(仿函数我们下面在实现的时候会具体讲)
函数声明 | 接口说明 |
|---|---|
priority_queue() / priority_queue(first, last) | 构造一个空的优先级队列 / 构造一个迭代器区间元素的优先级队列 |
empty() | 检测优先级队列是否为空,是返回 true,否则返回 false |
top() | 返回优先级队列中最大(最小)元素,即堆顶元素 |
push(x) | 在优先级队列中插入元素 x |
pop() | 删除优先级队列中最大(最小)元素,即堆顶元素 |
🌵 优先级队列的定义:
priority_queue<int> qe;
👇(默认情况下)
priority_queue<int, vector<int>, less<int>> qe;
//若将适配器容器改为list
priority_queue<int, list<int>, less<int>> qe;注: 若需要修改仿函数,则不可越过模板的第二个参数 Container 去直接设置仿函数,因为模板无法这样子自动识别你想修改的参数是第二个还是第三个,所以要将第二个参数也传过去(下面是定义,仔细观察缺省值部分)
template <class T, class Container = vector<T>,
class Compare = less<typename Container::value_type> > class priority_queue;如下就是错误示范:
priority_queue<int, greater<int>> ❌ //没有传第二个参数!💬 代码演示:
#include <vector>
#include <queue>
#include <functional> // greater算法的头文件
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;
// 如果要创建小堆,将第三个模板参数换成greater比较方式
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl;
}🚩 运行结果:9 0🔑 解读: 默认是用 vector 存储的,注意这里没有明确指定 less 还是 greater,所以默认为 less。
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;
}🚩 运行结果:
2018-10-30
2018-10-28 deque 是一个双端队列,是一种双开口的连续空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与 vector 比较,头插效率高,不需要搬移元素;与 list 比较,空间利用率比较高。
它提供了和
vector类似的接口但是底层的实现和vector却完全不同,最大的区别在于vector的所有元素都是连续的,但是deque不全是!因为deque也像list一样,按需申请空间,但是和list不一样的是,deque申请的空间不只是一个空间的大小,而是多个,按结构上来说,就像是一个动态的二维数组! deque在底层实现上是将一个连续的空间 分段进行管理,并将它们的首地址用一个 指针数组 进行管理,这样特殊的存储结构使得它在头部和尾部增加元素比vector更加高效,但是底层实现更为复杂,存储了很多额外信息。如果抛去在头部和尾部增加元素,在中间任意位置添加元素,它的效率比vector更高,但是比list要低。功能上其实是 vector 和 list 的结合!
❓ 问题: deque 看起来这么牛逼,那能不能用它代替我们的 vector 和 list 呢!
💡 解答: 肯定不行!因为 deque 比较适合头尾插入删除,但是不太适合大量的中间插入删除以及大量的随机访问,效率不如单单的使用 list 和 vector 那么高。实际中 deque 作为线性表结构,一般作为 stack 和 queue 的默认适配容器。所以 deque 是很难替 代 vector 和 list 的!只是说 deque 具有两者的功能而已!
deque(); //构造空的双端队列
deque(size_type n, const value_type& val = value_type()); //用n个值为val的元素构造双端队列
deque(InputIterator first, InputIterator last); //用[first, last)的区间构造双端队列
deque(const deque &x); //双端队列的拷贝构造函数iterator begin(); //返回deque起始位置迭代器
iterator end(); //返回deque最后一个元素下一个位置的迭代器
reverse_iterator rbegin(); //返回deque起始位置的反向迭代器(即end())
reverse_iterator rend(); //返回deque最后一个元素下一个位置的反向迭代器(begin())
const_iterator cbegin() const; //返回deque起始位置的const迭代器
const_iterator cend() const; //返回deque最后一个元素下一个位置的const迭代器
const_reverse_iterator crbegin() const; //返回deque起始位置的const反向迭代器(即crend())
const_reverse_iterator crend() const; //返回deque最后一个元素下一个位置的const反向迭代器(crbegin())size_type size() const; //返回deque中有效元素个数
bool empty() const; //检测deque是否为空,是返回true,否则返回false
void resize (size_type n, const T& val = T()); //将deque中的元素改变到sz,多出的空间用val填充reference operator[](size_type n); //返回deque中n位置上元素的引用
reference front(); //返回deque中首元素的引用
reference back(); //返回deque中最后一个元素的引用
void push_back(const value_type &val); //deque尾部插入元素val
void pop_back(); //删除deque尾部元素
void push_front(const value_type &val); //deque头部插入元素val
void pop_front(); //删除deque头部元素
//在deque的position位置插入值为val的元素
iterator insert(iterator position, const value_type &val);
//在deque的position位置插入n个值为val的元素
void insert(iterator position, size_type n,const value_type &val);
//在deque的position位置插入[first, last)区间中的元素
void insert(iterator position, InputIterator first, InputIterator last);
//删除deque中position位置的元素,并返回该位置的下一个位置
iterator erase(iterator position);
//删除deque中[first, last)区间中的元素,并返回last位置
iterator erase(iterator first, iterator last);
//交换两个deque中的内容
void swap(deque & x);
//将deque中的元素清空
void clear();
//在deque的position位置构造元素,将元素所需内容通过参数类表传入
iterator emplace(const_iterator position, Args && ... args);
void emplace_front(Args && ... args); //在deque的头部构造元素,元素的参数通过参数列表传入
void emplace_back(Args && ... args); //在deque的尾部构造元素,元素的参数通过参数列表传入void testDeque()
{
deque<int> dq;
dq.push_back(1);
dq.push_back(2);
dq.push_back(3);
dq.push_back(2);
deque<int>::iterator it = dq.begin();
while (it != dq.end())
{
cout << *it << " ";
++it;
}
cout << endl;
it = dq.erase(--it);
it = dq.begin();
while (it != dq.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}🚩 运行结果:
1 2 3 2
1 2 3 由于
deque在内存上并不完全是连续的因此想要保持deque的连续性,这个任务就落到了迭代器身上。在底层实现上,deque将一段一段连续的内存称为一个 缓冲区(buffer),并将这些缓冲区的首尾地址存储在一个 map 中用以映射,map 中一个存储缓冲区的地址对应一个 结点(node) 信息用于标记这个键值对,这样就构建好了基础架构。 在迭代器中存储了4个信息,分别是当前结点(cur),当前缓冲区的头(first),当前缓冲区的尾(last) 以及在 map 中用以标记当前缓冲区的地址的 结点(node) 信息。并且在deque内部已经存储好了两个迭代器start和finish用于标记deque的头和尾元素。这样即可完成将一段一段连续的空间在逻辑结构上构成一段连续空间的目的。 当从头遍历deque时,start迭代器中first和last已经从 map 中找到了第一个结点的缓冲区首尾信息并进行了保存,于是cur就从first开始遍历这个缓冲区,当遍历到last时就重新到 map 中寻找写一个结点的缓冲区收尾地址并且替换掉原来first和last值,继续遍历,这样即可完成遍历直到最后一个结点也遍历完。
双端队列deque是一个设计并不算成功的容器,如果要随机访问单纯的查询多一点可以用vector而且更加方便,如果需要频繁插入那么list效率又会跟高,因此deque并不常用,其最常用的地方就是在作为适配器stack和queue的底层存储容器。
deque 有点像 vector 和 list 的结合体 ,我们把 vector 和 list 也拉出来进行优缺点的对比再合适不过了:
容器 | 优点 | 缺点 |
|---|---|---|
vector | 适合尾插尾删,随机访问 | ① 不适合头部或者中部的插入删除,效率低下,需要挪动数据。② 扩容有一定程度的性能消耗,还可能存在一定程度的空间浪费。 |
list | ① 适合任意位置的插入删除,效率高,O(1)时间复杂度 ② 按需申请空间,空间利用率比较高 | ① 不支持随机访问 ② CPU 高速缓存命中率低 |
deque | ① 头部和尾部插入数据效率不错 ② 支持随机访问 ③ 扩容代价小 ④CPU高速缓存命中高 | ① 中部的插入删除效率不行② 不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,③ 不够极致。 |
❓ 那什么场景适合用 deque 呢?
虽然不够极致但是还是有用武之地的:大量头尾插入删除,偶尔随机访问的情况可以使用 deque。
stack是一种后进先出的特殊线性数据结构,因此只要具有 push_back() 和 pop_back() 操作的线性结构,都可以作为 stack 的底层容器,比如 vector 和 list 都可以; queue 是先进先出的特殊线性数据结构,只要具有 push_back() 和 pop_front() 操作的线性结构,都可以作为 queue 的底层容器,比如 list。但是 STL 中对 stack 和 queue 默认选择 deque 作为其底层容器,主要是因为:
结合了 deque 的优点,而完美的避开了其缺陷。
在模拟实现 stack 和 queue 和 priority_queue,我们得重新来回顾一下容器适配器,也可以参考 list 笔记中的解释。
也就是说,容器适配器就是将已经存在的容器(如vector)拿过来实现适配器,达到复用的效果!
从栈的接口中可以看出,栈实际是一种特殊的 vector,因此使用 vector 完全可以模拟实现 stack。
但是为了和 STL 中的接口保持一致:STL 增加了一个模板参数 Container,利用 Container 来进行转换,而 STL 中还用 deque 去作为默认的适配器实现 stack,所以我们这里就统一使用 deque 作为默认适配器!
❓ 思考: 可否用 string 作为适配器? 勉强可以,只要你是顺序容器都可以。但是用 string 是有风险的,可能会出现溢出和截断。
#pragma once
#include<iostream>
#include<deque>
using std::deque; //记得要引入std中的deque
namespace liren
{
//可以任意设置容器适配器,只要满足接口功能要求即可
//template<class T, class Container = vector<int>>
//template<class T, class Container = list<int>>
template<class T, class Container = deque<T>>
class stack
{
public:
void push(const T& val) // 对于栈而言,入栈就是尾插
{
_con.push_back(val);
}
void pop() // 对于栈而言,出栈就是尾删
{
_con.pop_back();
}
T& top() // 返回尾上数据
{
return _con.back();
}
const T& top() const //const版本
{
return _con.back();
}
size_t size() const // 返回栈大小
{
return _con.size();
}
bool empty() const // 返回栈是否为空
{
return _con.empty();
}
private:
Container _con;
};
}测试代码:
void Testmystack()
{
liren::stack<int> st;
st.push(1);
st.push(4);
st.push(3);
st.push(2);
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
cout << endl;
//也可以传不同的适配器,照样能跑
liren::stack<int, vector<int>> s;
s.push(1);
s.push(4);
s.push(3);
s.push(2);
while (!s.empty())
{
cout << s.top() << " ";
s.pop();
}
cout << endl;
}🔺 总结: 从上层角度来看,其默认是一个双端队列,我底层用什么存,去实现这个栈并不重要,只要其符合要求就行。它保存的是栈的性质,复用的是容器的代码。
同样,queue 也用 deque 来作为默认容器实现,与 stack 的代码基本没什么变化!
queue 是先进先出的,queue 的 push 仍然是尾部的插入,而 pop 需要支持头部的删除!
值得注意的是,queue 不能用 vector 去适配,因为 vector 压根就没有 pop_front() 等接口。
#pragma once
#include<iostream>
#include<deque>
using std::deque; //同样也要引入std中的deque
namespace liren
{
template<class T, class Container = deque<int>>
class queue
{
public:
void push(const T& val)
{
_con.push_back(val);
}
void pop()
{
_con.pop_front();
}
T& back()
{
return _con.back();
}
const T& back() const
{
return _con.back();
}
T& front()
{
return _con.front();
}
const T& front() const
{
return _con.front();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
}测试代码:
void Testmyqueue()
{
liren::queue<int> q;
q.push(1);
q.push(3);
q.push(4);
q.push(2);
while (!q.empty())
{
cout << q.front() << " ";
q.pop();
}
cout << endl;
//✔也可以传list容器,照样能跑
liren::queue<int, list<int>> q1;
q1.push(1);
q1.push(3);
q1.push(4);
q1.push(2);
while (!q1.empty())
{
cout << q1.front() << " ";
q1.pop();
}
cout << endl;
//❌这是跑不过的,因为vector不支持pop_front()等函数
liren::queue<int, vector<int>> q2;
q2.push(1);
q2.push(1);
q2.push(1);
q2.push(1);
while (!q2.empty())
{
cout << q2.front() << " ";
q2.pop();
}
cout << endl;
}在模拟实现优先级队列之前,我们得先来了解一个概念,那就是仿函数!
❓ 问题: 为什么实现 priority_queue 要了解仿函数? 💡 解答: 其实 priority_queue 本质就是一个堆,而且默认是个大堆!也就是说 priority_queue 第一个元素是最大的。 那如果我们需要的是个小堆呢?那该怎么办?所以这个时候我们就得传仿函数给模板当参数,让模板来控制我们想要的。
📚 概念: 使一个类的使用看上去像一个函数。可以像函数一样使用的对象,称为函数对象。
其实现就是在类中重载 operator(),使得该类具备类似函数的行为,就是一个仿函数类了。
C语言优先级,() 圆括号使用形式为 表达式 或 “作为函数形参列表的括号” 我们这里重载的其实就是:函数名(形参表)
👁🗨 比如下面的代码就是比较 重载的() 与 函数 的区别:
//仿函数 -- 函数对象(可以像函数一样去使用)
struct lessInt
{
bool operator()(int left, int right)
{
return left < right;
}
};
//函数的正常形式
bool lessFunc(int left, int right)
{
return left < right;
}
void testFunctor()
{
//仿函数的使用
lessInt less;
cout << less(1, 2) << endl;
//正常函数的使用
cout << lessFunc(1, 2) << endl;
}🚩 运行结果:
1
1❓ 思考:我们在C阶段不是学过函数指针么,用函数指针不香吗?
函数指针确实可以做到这些功能。但是在这里函数指针跟仿函数比,一点都不香! 函数的指针在一些地方用起来非常复杂。
比如下面的代码:
static void( * set_malloc_handler(void (*f)()))()
{
void (* old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}这个函数的参数是什么?函数的返回值是什么?
这就很阴间了,这就是函数指针的杰作…… 所以 C++ 搞出了仿函数,简化了好多。
📚 仿函数的优势: 很多场景,替换了函数指针。
priority_queue 的底层结构就是堆,因此只需对堆进行通用的封装即可。
默认情况下的优先级队列是大堆,那么如果我们像让其变成小堆,则要我们另行传仿函数,这个只需要我们构造即可。
有了仿函数的了解之后,我们就可以控制大小堆的问题啦!那我们先把 priority_queue 的主体结构搭建起来,因为 priority_queue 本质是堆,所以我们用 vector 来作为其默认的容器适配器!
#pragma once
#include<iostream>
#include<vector>
using std::vector;
namespace liren
{
//这里的Compare就是我们的仿函数!
template<class T, class Container = std::vector<T>, class Compare = less<T>>
class priority_queue
{
public:
// 创造空的优先级队列
priority_queue() : _con()
{}
private:
Container _con;
};
}与 stack 和 queue 一样,优先级队列的几个接口也是一样的实现方法,如 size(),empty() 和 top(),所以我们先将其实现!
size_t size() const
{
return _con.size();
}
// 堆顶元素不允许修改,因为:堆顶元素修改可以会破坏堆的特性
const T& top() const
{
return _con.front();
}
bool empty() const
{
return _con.empty();
}可以从上面的框架代码看到,对于大堆,STL 中 默认的取名叫做 less,而对于小堆,我们要取名为 greater !
❓ 是不是觉得很奇怪,为什么大堆反而取名为 less 呢? 💡 解答: 以我的理解,大堆是降序的,是依次变小的,所以用 less, 其实也是合理的,而 greater 则反之!
📡 代码如下:
//less是用来比较谁小的
template<class T>
struct less
{
bool operator()(const T& left, const T& right)
{
return left < right;
}
};
//greater是用来比较谁大的
template<class T>
struct greater
{
bool operator()(const T& left, const T& right)
{
return left > right;
}
};因为 priority_queue 是个堆,所以我们等会实现插入删除的时候是需要重新调整堆的大小顺序的,所以我们先将其调整算法写出来!
💃 注意一个点,就是这种调整算法是不太允许被外面的其他接口随便调的,所以我们要将其设为 private。 💃 还有一个点,就是向上调整算法中,parent 和 child 最好不要将其类型设为 size_t ,因为可能会出现小于0的情况。
这个知识点并不难,只要还是堆的知识,所以这里直接将代码写出来:
private:
//向下调整算法
void AdjustDown(int parent)
{
Compare Com;
size_t child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && Com(_con[child], _con[child + 1]))
{
child += 1;
}
if (Com(_con[parent], _con[child]))
{
::swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
//向下调整算法
void AdjustUp(int child)
{
Compare Com;
int parent = (child - 1) >> 1;
while (child > 0)
{
if (Com(_con[parent], _con[child]))
{
::swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) >> 1;
}
else
break;
}
}优先级队列相较于普通的队列,其区别主要是在 push 和 pop 上,
即需要在插入 / 删除数据的同时,增添调整的功能,并且 STL 的优先级队列是由堆来维护的:
入队时将数据推入后,从最后一个元素开始进行上调。 出队时采用堆的删除手法,将根元素(即队头)和最后一个元素进行交换,并调用尾删掉队头。 既然用了头尾交换法,我们就不得不对队列重新调整,所以从根位置进行下调,即可完成出队。
void push(const T& val)
{
_con.push_back(val);
AdjustUp(_con.size() - 1);
}
void pop()
{
if (empty())
return;
::swap(_con.front(), _con.back()); //多调用容器的接口复用
_con.pop_back();
AdjustDown(0);
}对于优先级队列,我们直接用 vector 的构造函数来完成迭代器构造函数即可!
最主要的问题是,用 vector 的迭代器区间来构造 vector 后,我们要对其进行建堆,让其变成一个名副其实的优先级队列!
template<iterator>
priority_queue(iterator first, iterator last)
: _con(first, last) //这里其实就调用了vector的构造函数完成了构造
{
// 将_con中的元素调整成堆的结构
for (int parent = (_con.size() - 1 - 1) >> 1; parent >= 0; parent--)
{
AdjustDown(parent);
}
}void testMypriority_queue()
{
liren::priority_queue<int> pq;
pq.push(1);
pq.push(2);
pq.push(3);
pq.push(4);
pq.push(5);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
//也可以将其改为小堆顺序
liren::priority_queue<int, vector<int>, liren::greater<int>> pq2;
pq2.push(1);
pq2.push(2);
pq2.push(3);
pq2.push(4);
pq2.push(5);
while (!pq2.empty())
{
cout << pq2.top() << " ";
pq2.pop();
}
cout << endl;
//还可用迭代器构造区间
vector<int> v{ 5,1,4,2,3,6 };
liren::priority_queue<int, vector<int>, liren::greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl;
q2.pop();
q2.pop();
cout << q2.top() << endl;
}#pragma once
#include<iostream>
#include<deque>
using std::deque; //记得要引入std中的deque
namespace liren
{
template<class T, class Container = deque<T>>
class stack
{
public:
void push(const T& val) // 对于栈而言,入栈就是尾插
{
_con.push_back(val);
}
void pop() // 对于栈而言,出栈就是尾删
{
_con.pop_back();
}
T& top() // 返回尾上数据
{
return _con.back();
}
const T& top() const //const版本
{
return _con.back();
}
size_t size() const // 返回栈大小
{
return _con.size();
}
bool empty() const // 返回栈是否为空
{
return _con.empty();
}
private:
Container _con;
};
}#pragma once
#include<iostream>
#include<deque>
using std::deque; //同样也要引入std中的deque
namespace liren
{
template<class T, class Container = deque<int>>
class queue
{
public:
void push(const T& val)
{
_con.push_back(val);
}
void pop()
{
_con.pop_front();
}
T& back()
{
return _con.back();
}
const T& back() const
{
return _con.back();
}
T& front()
{
return _con.front();
}
const T& front() const
{
return _con.front();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
}#pragma once
#include<iostream>
#include<vector>
using std::vector;
namespace liren
{
//less是用来比较谁小的
template<class T>
struct less
{
bool operator()(const T& left, const T& right) const
{
return left < right;
}
};
//greater是用来比较谁大的
template<class T>
struct greater
{
bool operator()(const T& left, const T& right) const
{
return left > right;
}
};
//这里的Compare就是我们的仿函数!
template<class T, class Container = std::vector<T>, class Compare = less<T>>
class priority_queue
{
public:
// 创造空的优先级队列
priority_queue() : _con()
{}
template<class Iterator>
priority_queue(Iterator first, Iterator last)
: _con(first, last) //这里其实就调用了vector的构造函数完成了构造
{
// 将_con中的元素调整成堆的结构
for (int parent = (_con.size() - 1 - 1) >> 1; parent >= 0; parent--)
{
AdjustDown(parent);
}
}
void push(const T& val)
{
_con.push_back(val);
AdjustUp(_con.size() - 1);
}
void pop()
{
if (empty())
return;
::swap(_con.front(), _con.back()); //多调用容器的接口复用
_con.pop_back();
AdjustDown(0);
}
size_t size() const
{
return _con.size();
}
// 堆顶元素不允许修改,因为:堆顶元素修改可以会破坏堆的特性
const T& top() const
{
return _con.front();
}
bool empty() const
{
return _con.empty();
}
private:
//向下调整算法
void AdjustDown(int parent)
{
Compare Com;
size_t child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && Com(_con[child], _con[child + 1]))
{
child += 1;
}
if (Com(_con[parent], _con[child]))
{
::swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
//向下调整算法
void AdjustUp(int child)
{
Compare Com;
int parent = (child - 1) >> 1;
while (child > 0)
{
if (Com(_con[parent], _con[child]))
{
::swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) >> 1;
}
else
break;
}
}
private:
Container _con;
};
}#define _CRT_SECURE_NO_WARNINGS
#include <vector>
#include <iostream>
#include <queue>
#include<list>
#include <functional> // greater算法的头文件
#include<deque>
#include "stack.h"
#include "queue.h"
#include "priority_queue.h"
using namespace std;
void test_queue() {
/* 创建一个存储整型的队列 */
queue<int> Q;
/* 入队 */
Q.push(1);
Q.push(2);
Q.push(3);
Q.push(4);
/* 打印队列 */
while (!Q.empty()) { // 如果队列不为空则进入循环
cout << Q.front() << " "; // 打印队头元素
Q.pop(); // 出队
}
cout << endl;
}
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;
// 如果要创建小堆,将第三个模板参数换成greater比较方式
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl;
}
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 TestPriorityQueue2()
{
// 大堆,需要用户在自定义类型中提供<的重载
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;
}
void testDeque()
{
deque<int> dq;
dq.push_back(1);
dq.push_back(2);
dq.push_back(3);
dq.push_back(2);
deque<int>::iterator it = dq.begin();
while (it != dq.end())
{
cout << *it << " ";
++it;
}
cout << endl;
it = dq.erase(--it);
it = dq.begin();
while (it != dq.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
void Testmystack()
{
liren::stack<int> st;
st.push(1);
st.push(4);
st.push(3);
st.push(2);
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
cout << endl;
//也可以传不同的适配器,照样能跑
liren::stack<int, vector<int>> s;
s.push(1);
s.push(4);
s.push(3);
s.push(2);
while (!s.empty())
{
cout << s.top() << " ";
s.pop();
}
cout << endl;
}
void Testmyqueue()
{
liren::queue<int> q;
q.push(1);
q.push(3);
q.push(4);
q.push(2);
while (!q.empty())
{
cout << q.front() << " ";
q.pop();
}
cout << endl;
//也可以传list容器,照样能跑
liren::queue<int, list<int>> q1;
q1.push(1);
q1.push(3);
q1.push(4);
q1.push(2);
while (!q1.empty())
{
cout << q1.front() << " ";
q1.pop();
}
cout << endl;
/*liren::queue<int, vector<int>> q2;
q2.push(1);
q2.push(1);
q2.push(1);
q2.push(1);
while (!q2.empty())
{
cout << q2.front() << " ";
q2.pop();
}
cout << endl;*/
}
//仿函数 -- 函数对象(可以像函数一样去使用)
struct lessInt
{
bool operator()(int left, int right)
{
return left < right;
}
};
//函数的正常形式
bool lessFunc(int left, int right)
{
return left < right;
}
void testFunctor()
{
//仿函数的使用
lessInt less;
cout << less(1, 2) << endl;
//正常函数的使用
cout << lessFunc(1, 2) << endl;
}
void testMypriority_queue()
{
liren::priority_queue<int> pq;
pq.push(1);
pq.push(2);
pq.push(3);
pq.push(4);
pq.push(5);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
//也可以将其改为小堆顺序
liren::priority_queue<int, vector<int>, liren::greater<int>> pq2;
pq2.push(1);
pq2.push(2);
pq2.push(3);
pq2.push(4);
pq2.push(5);
while (!pq2.empty())
{
cout << pq2.top() << " ";
pq2.pop();
}
cout << endl;
//还可用迭代器构造区间
vector<int> v{ 5,1,4,2,3,6 };
liren::priority_queue<int, vector<int>, liren::greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl;
q2.pop();
q2.pop();
cout << q2.top() << endl;
}
int main()
{
//test_queue();
//TestPriorityQueue2();
//testDeque();
//Testmyqueue();
//testFunctor();
testMypriority_queue();
return 0;
}