【C++】stack、queue和priority_queue的使用及模拟实现(包括deque、仿函数、反向迭代器)
目录
1.stack
1.1 介绍
我们通过学习数据结构时知道栈和队列是特殊的线性表,可以通过顺序表或链表来实现,在C++中把这种模式称为适配器模式,用已有的东西封装成你想要的东西。
容器适配器即是对特定类封装作为其底层的容器,并提供一组特定
的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。stack的默认底层容器是deque。
1.2 stack的OJ
对于这道题,我们可以用两个栈来解决,一个普通栈用来入所有的数据,另一个minst用来入小于等于上次入栈元素的元素。出栈时,普通栈一定出栈,minst只有当其栈顶元素等于普通栈的栈顶元素才出栈。此时minst的栈顶元素就是普通栈中最小的元素。
class MinStack {
public:
MinStack()
{}
void push(int val)
{
_st.push(val);
if (_minst.empty() || val <= _minst.top())
{
_minst.push(val);
}
}
void pop()
{
if (_st.top() == _minst.top())
{
_minst.pop();
}
_st.pop();
}
int top()
{
return _st.top();
}
int getMin()
{
return _minst.top();
}
private:
stack<int> _st;
stack<int> _minst;
};
依次遍历入栈序列入栈,然后拿入栈元素和出栈序列的首个元素比较,如果相等就pop掉,不相等就再将入栈序列后面的元素入栈,再进行比较。
如果入栈序列和出栈序列相匹配,最终的st应该是空的,不为空就说明不匹配。
class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV)
{
stack<int> st;
int j = 0;
for (auto e : pushV)
{
st.push(e);
while (!st.empty() && st.top() == popV[j])
{
++j;
st.pop();
}
}
return st.empty();
}
};
逆波兰式(Reverse Polish Notation,RPN,或逆波兰记法),也叫后缀表达式(将运算符写在操作数之后)。
对于这道题,要做的就是要将题中给的后缀表达式转换成我们熟悉的中缀表达式。
转换方式:依次遍历字符串数组tokens,操作数入栈,遇见操作符就取栈顶的两个元素出栈,运算结果重新入栈。
class Solution {
public:
int evalRPN(vector<string>& tokens)
{
stack<int> st;
for (auto& str : tokens)
{
if (str == "+" || str == "-" || str == "*" || str == "/")
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
switch(str[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(str));
}
}
return st.top();
}
};
拓展:
1.中缀表达式转后缀表达式:遍历数组,遇见操作数则输出,遇见操作符,如果栈为空,进栈;栈不为空,如果该操作符优先级高于栈顶操作符,进栈,否则出栈顶操作符。
2.带上括号的中缀表达式转后缀表达式:在上一个的基础上,把()看作操作符,()的优先级最低,(不参与比较直接入栈,)参与比较直至遇到(。
1.3 stack的模拟实现
由于我们还没有接触过deque,这里可以用vector来作为底层容器实现stack。
stack的模拟实现很简单,只需要去模拟其后进先出的特性即可。
namespace lgr
{
template <class T, class Container = vector<T>>
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
const T& top()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
}
2.queue
2.1 queue简介
队列也是一种容器适配器,由于出队列要进行头删,所以队列的底层容器一般是list或者deque,默认是deque。
2.2 queue模拟实现
namespace lgr
{
template <class T, class Container = std::list<T>>
class queue
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
const T& front()
{
return _con.front();
}
const T& back()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
}
3.deque
3.1 deque的介绍
deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:
3.2 deque的缺陷
与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。
与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。
但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。
3.3 为什么选择deque作为stack和queue的底层默认容器
stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:
- stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
- 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。
结合了deque的优点,而完美的避开了其缺陷。
4.仿函数
仿函数实际上是一个类,用来去模仿函数的操作,比方说下面用类模拟了两个比较函数,在使用时只需将类实例化出一个对象,就可以模仿函数的操作进行使用。
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;
}
};
int main()
{
Less<int> lessfunc;
cout << lessfunc(1, 2);
return 0;
}
仿函数可以替代函数指针,之前在实现qsort时用到函数指针,在这里就可以直接传仿函数的匿名函数,不用再受函数指针的折磨。
5.priority_queue
5.1 priority_queue的介绍
priority_queue叫做优先级队列,实际上就是堆,默认底层容器是vector。
5.2 priority_queue的使用
优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。
注意:默认情况下priority_queue是大堆。
empty():检测容器是否为空
size():返回容器中有效元素个数
front():返回容器中第一个元素的引用
push_back():在容器尾部插入元素
pop_back():删除容器尾部元素
int main()
{
vector<int> v{ 3,2,7,6,0,4,1,9,8,5 };
// 默认情况下是大堆
priority_queue<int> q1(v.begin(), v.end());
cout << q1.top() << endl;
// 想要创建小堆需要加上仿函数greator
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl;
}
5.3 priority_queue的模拟实现
想要在priority_queue中使用仿函数,只需要添加一个模板参数用来接收仿函数,默认情况下位less
namespace lgr
{
template<class T>
struct less
{
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
template<class T>
struct greater
{
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:
priority_queue()
{}
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
:_con(first, last)
{
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i)
{
adjust_down(i);
}
}
void adjust_up(int child)
{
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;
}
}
}
void adjust_down(int parent)
{
int 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[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x);
adjust_up(_con.size()-1);
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}
size_t size()
{
return _con.size();
}
const T& top()
{
return _con.front();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
Compare com;
};
}
如果在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;
};
6.反向迭代器
反向迭代器的++就是正向迭代器的 - -,反向迭代器的 - -就是正向迭代器的++,rbegin就是end,rend就是begin,我们可以把反向迭代器当作正向迭代器的适配器。
模拟实现:
namespace lgr
{
template<class Iterator, class Ref, class Ptr>
struct ReverseIterator
{
typedef ReverseIterator<Iterator, Ref, Ptr> self;
Iterator _cur;
ReverseIterator(Iterator it)
:_cur(it)
{}
Ref operator*()
{
Iterator tmp = _cur;
--tmp;
return *tmp;
}
Ptr operator->()
{
Iterator tmp = _cur;
--tmp;
return &(*tmp);
}
self& operator++()
{
--_cur;
return *this;
}
self operator++(int)
{
self tmp(*this);
--_cur;
return tmp;
}
self& operator--()
{
++_cur;
return *this;
}
self operator--(int)
{
self tmp(*this);
++_cur;
return tmp;
}
bool operator!=(const self& s)
{
return _cur != s._cur;
}
bool operator==(const self& s)
{
return _cur == s._cur;
}
};
}
vector和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 ReverseIterator<iterator, T&, T*> reverse_iterator;
typedef ReverseIterator<iterator, const T&, const T*> const_reverse_iterator;
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
iterator begin()
{
return iterator(_head->_next);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator end() const
{
return const_iterator(_head);
}
void empty_init()
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();
}
template <class Iterator>
list(Iterator first, Iterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
/*list(const list<T>& lt)
{
empty_init();
for (auto e : lt)
{
push_back(e);
}
}*/
void swap(list<T>& tmp)
{
std::swap(_head, tmp._head);
}
list(const list<T>& lt)
{
empty_init();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
void push_back(const T& x)
{
/*node* tail = _head->_prev;
node* new_node = new node(x);
tail->_next = new_node;
new_node->_prev = tail;
new_node->_next = _head;
_head->_prev = new_node;*/
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
void clear()
{
iterator pos = begin();
while (pos != end())
{
erase(pos++);
}
}
void insert(iterator pos, const T& x)
{
node* cur = pos._node;
node* prev = cur->_prev;
node* new_node = new node(x);
prev->_next = new_node;
new_node->_prev = prev;
new_node->_next = cur;
cur->_prev = new_node;
}
iterator erase(iterator pos)
{
assert(pos != end());
node* prev = pos._node->_prev;
node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
return pos;
}
private:
node* _head;
};
}