一、Stack
二、Queue
三、几个应用例题
(1)
class MinStack {
public:
//构造函数没什么写的
MinStack() {
}
void push(int val) {
//s1一定要入栈
s1.push(val);
//如果val比s2的顶部元素还要小,s2也入栈
if(s2.empty()||s2.top()>=val)
{
s2.push(val);
}
}
void pop() {
//如果s1和s2的栈顶元素相同,s2出栈
if(s1.top()==s2.top())
{
s2.pop();
}
//s1一定要出栈
s1.pop();
}
int top() {
return s1.top();
}
int getMin() {
//只需要返回s2的栈顶元素就好
return s2.top();
}
private:
stack<int> s1;//用于存整个栈的内容
stack<int> s2;//用于筛选最小值
};
(2)
(3)
解释:这个题涉及的是中缀表达式转化为后缀表达式
先看看后缀表达式的计算方法:
1.先把数字全部入栈
2.遇到算术符:取栈顶两个元素进行计算,把计算结果入栈
中缀表达式转化为后缀表达式:
代码展示:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
//创建栈
stack<int> st;
//依次遍历表达式
for(auto& e:tokens)
{
//遇到符号
if(e=="+"||e=="-"||e=="*"||e=="/")
{
//依次取出栈中的两个数
int right=st.top();
st.pop();
int left=st.top();
st.pop();
//跟据操作符进行操作,操作后的数要返回栈中
switch(e[0])
{
case '+':
st.push(left+right);
break;
case '-':
st.push(left-right);
break;
case '*':
st.push(left*right);
break;
case '/':
st.push(left/right);
break;
}
}
//不是字符时,把这个数字字符转化为数字放到栈里面
else
{
st.push(stoi(e));
}
}
//返回栈中最后剩下的元素
return st.top();
}
};
(4)
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
//定义二维数组,依次存放每一层的值
vector<vector<int>> vv;
//创建队列辅助
queue<TreeNode*> q;
//用于记录每一层节点个数
int levelsize=0;
//当第一个节点不为空时
if(root)
{
//入根节点
q.push(root);
levelsize=1;
}
//当队列不为空,继续进行,为空时,表明已经层序遍历完毕
while(!q.empty())
{
//创建v用于存放每一层的节点
vector<int> v;
//对这一层的节点进行遍历
for(int i=0;i<levelsize;i++)
{
//取这一层的节点并删除
TreeNode* front=q.front();
q.pop();
//把这一层节点的值放到v中
v.push_back(front->val);
//如果这层节点有左右孩子,就把它们放到队列中
if(front->left)
q.push(front->left);
if(front->right)
q.push(front->right);
}
//通过上面的循环,实现了把上一层的节点值存在v中,并把下一层的节点存在队列中
//把这一层的节点值存在总的二维数组中
vv.push_back(v);
//下一层节点的个数等于这是队列的大小
levelsize=q.size();
}
//返回二维数组
return vv;
}
};
四、栈和队列的底层实现
#include<iostream>
using namespace std;
#include<vector>
#include<list>
namespace djlstack
{
template <class T, class container>
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
void top()
{
_con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
container _con = vector<T>;
};
}
#include<iostream>
using namespace std;
#include<vector>
#include<list>
namespace djlqueue
{
template <class T, class container>
class queue
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
void top()
{
_con.front();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
container _con = vector<T>;
};
}
像栈和队列这样通过利用其他容器进行底层实现,我们叫做容器适配器,通过模板参数实现数组栈和链式栈可以相互转化,不需要写拷贝析构构造,因为可以调用容器的默认构造
五、双端队列 deque
(1)简介
它实际上不是队列,是一个两边都能插入和删除的容器,是一个超综合的容器,头尾操作效率还行,但是随机访问效率太低
(2)简单看一下底层
由一个中控数组存储好几个vector的指针,中控数组可以扩容且扩容容易(因为存放的是指针,扩容量小)
相比vector:
1、极大缓解了扩容问题/头插头删问题
2、[]不够极致,计算在哪个buff,在哪个buff的第几个
相比list:
1、可以支持下标随机访问
2、cpu高速缓存效率不错
3、头尾插入删除都不错,但是中间插入删除需要进行更多的遍历
六、优先级队列
(1)简介
利用容器适配器,底层是一个堆
定义时需要三个模板参数(数据类型,存数据的容器类型(默认是vector),控制大堆还是小堆的模板参数(默认是less大堆))
#include<iostream>
using namespace std;
#include<queue>
#include<vector>
int main()
{
//小堆
priority_queue<int, vector<int>> q1;//默认是小堆
//priority_queue<int, vector<int>, less<int>> q2;
//大堆
priority_queue<int, vector<int>,greater<int>> q1
return 0;
}
它的构造函数支持迭代器区间初始化,但是吧支持迭代器访问
(2)一道例题
解析:实质是就是二叉树部分的top—k问题,建大堆,然后依次取最大数
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
//建堆存在pq中
priority_queue<int> pq(nums.begin(),nums.end());
while(--k)
{
pq.pop();
}
return pq.top();
}
};
(3)模拟实现
#include<vector>
#include<iostream>
using namespace std;
namespace djl
{
template<class T, class container = vector<T>>
class priority_queue
{
public:
void AdjustDown(int i)
{
int parent = i;
int child = 2 * i + 1;
while (child < _con.size())
{
//找到大孩子
if (_con.size() > (child + 1) && _con[child] < _con[child + 1])
{
child++;
}
if (_con[parent] < _con[child])
{
swap(_con[parent], _con[child]);
parent = child;
int child = 2 * i + 1;
}
else
{
break;
}
}
}
void AdjustUp(int i)
{
int child = i;
int parent = (child - 1) / 2;
while (child >= 0)
{
if (_con[child] > _con[parent])
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
template<class inputiterator>
priority_queue(inputiterator first, inputiterator last)
{
while (first != last)
{
_con.push_back(*first);
++first;
}
//建大堆
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(i);
}
}
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();
}*/
size_t size()
{
return _con.size();
}
private:
container _con;
};
};
七、仿函数
(1)定义
可以看到,用Less类定义出来的对象就像函数一样,可以像函数一样使用
和类模板结合更香欧~~
(2)利用仿函数在优先级队列中实现大小堆的建立
//小于仿函数
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;
}
};
//模板多定义一个参数,用于实现仿函数的对象的创建
template<class T, class container = vector<T>, class Compare = less<T>>
class priority_queue
{
public:
void AdjustDown(int i)
{
//创建仿函数对象进行比较
Compare com;
int parent = i;
int child = 2 * i + 1;
while (child < _con.size())
{
//找到大孩子
//将比较部分替换为仿函数比较
if (_con.size() > (child + 1) && com(_con[child], _con[child + 1]))
{
child++;
}
if (_con[parent] < _con[child])
{
swap(_con[parent], _con[child]);
parent = child;
int child = 2 * i + 1;
}
else
{
break;
}
}
}
void AdjustUp(int i)
{
int child = i;
int parent = (child - 1) / 2;
while (child >= 0)
{
//将比较部分替换为仿函数比较
if (com(_con[parent], _con[child]))
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
private:
container _con;
}
};
经过上述操作,即可通过仿函数,在定义优先级队列时通过第三个模板参数的实例化实现大堆还是小堆的建立
八、反向迭代器的底层实现
库中使用适配器模式构建反向迭代器(用正向迭代器去构建反向迭代器)
反向迭代器相较于正向迭代器只有头,尾指针,和++,--有变化 ,要特别注意,解引用是返回当前指针的前一个位置的值
跟据--解引用,可以得出两种迭代器的指针分布应该是这样的(左边是链表右边是顺序表)
看下面这个示例(如果不是--解引用,第一个值就是随机值)
接下来看看底层实现
//list节点包装
template<class T>
struct list_node
{
list_node(const T& val = T())
:_next(nullptr)
, _prev(nullptr)
, _val(val)
{
}
list_node<T>* _next;
list_node<T>* _prev;
T _val;
};
//list的定义(仅迭代器部分)
template<class T>
class list
{
typedef list_node<T> Node;
public:
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
typedef
iterator begin()
{
return _head->_next;
}
iterator end()
{
return _head;
}
const_iterator begin()const
{
return _head->_next;
}
const_iterator end()const
{
return _head;
}
iterator insert(iterator pos,const T& x)
{
Node* newnode = new Node(x);
Node* cur = pos._node;
Node* prev = cur->_prev;
cur->_prev = newnode;
newnode->_next = cur;
prev->_next = newnode;
newnode->_prev = prev;
return newnode;
}
iterator erase(iterator pos)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
delete cur;
prev->_next = next;
next->_prev = prev;
return next;
}
private:
Node* _head;
};
//这个是list的正向迭代器
template<class T,class Ref,class Ptr>
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator<T,Ref,Ptr> self;
Node* _node;
list_iterator(Node* node)
:_node(node)
{
}
Ref operator*()
{
return _node->_val;
}
//前置++
self& operator++()
{
_node = _node->_next;
return *this;
}
//后置++
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
bool operator!=(const self& it)
{
return _node != it._node;
}
bool operator==(const self& it)
{
return _node == it._node;
}
Ptr operator->()
{
return &_node->_val;
}
};
//vector的正向迭代器就是指针,不需要进行包装
template<class T>
class vector
{
public:
//重命名
typedef T* iterator;
typedef const T* const_iterator;
//迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
//插入
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _endoftorage)
{
size_t len = pos - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
//更新pos防止迭代器失效
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *(end);
end--;
}
*pos = x;
_finish++;
return pos;
}
//删除
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos;
while (it != _finish)
{
*(it) = *(it + 1);
it++;
}
_finish--;
return pos;
}
private:
iterator _start;
iterator _finish;
iterator _endoftorage;
};
//反向迭代器,任何有正向迭代器的容器都可以包含使用,存在头文件中
template<class Iterator,class Ref,class Ptr>
struct ReverseIterator
{
typedef ReverseIterator<Iterator, Ref, Ptr>Self;//类名+模板参数=类型
Iterator _it;
ReverseIterator(Iterator it)
:_it(it)
{
}
Ref operator*()
{
Iterator tmp = _it;
return *(--tmp);
}
Ptr operator->()
{
return &(operator*());
}
Self& operator++()
{
--_it;
return *this;
}
Self& operator--()
{
++_it;
return *this;
}
int operator!=(const Self& s)const
{
return _it != s._it;
}
};
接下来看看,反向迭代器如何作为头文件使用:在各个stl库中包含反向迭代器的头文件,然后typedef一下,再写一下reend和rebegin的函数,然后就可以使用了。(下面以list为例)
下面在这个是main函数中的测试代码: