【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作为其底层容器,主要是因为:

  1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
  2. 在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;
	};
}