C++: Use of stack and queue and underlying implementation

1.Adapter mode

Adapter is a design pattern (code design experience) that converts the interface of a class into another interface that the user wants . The bottom layer of the container introduced in this article is actually implemented using other containers, and the interface provided to users is just a simple encapsulation of the interfaces of other containers .


2.Introduction and use of stack

2.1Introduction to stack

  1. stack is a stack.

  2. Stack is a container adapter, which is characterized by last-in-first-out . Its deletion can only insert and extract elements from one end of the container .

  3. stack is a class template (template <class T, class Container = deque<T> > class stack). The underlying container of the stack can be any standard container class template or some other specific container classes. These container classes should support the following operations :
    (1) empty: empty operation
    (2) back: obtain tail element operation
    (3) push_back: tail insert element operation
    (4) pop_back: tail delete element operation

  4. The standard containers vector, deque, and list all meet these requirements. By default, if no specific underlying container is specified for the stack, deque (double-ended queue, discussed later) is used by default.

  5. Use <stack> to include header files.
    Insert image description here

2.2Use of stack

Common interface description:

function illustrate
stack() Constructor, constructs an empty stack
empty() Determine whether the stack is empty, return true if empty, otherwise return false
size() Returns the number of elements in the stack
top() Get a reference to the data on the top of the stack
push(val) Push element val onto the stack
pop() Pop the top element of the stack

Exercise:
Minimum Stack

//这个题目的要点是记录最小值,但出栈后可能最小值可能变化,只用一个变量记录不够
//我们可以设计两个栈:
//(1)s栈,正常出入数据
//(2)min栈,在s栈入栈时进行判断,如果自身为空或者新入栈的元素小于等于min栈顶就入栈
//在s栈出栈时也进行判断,如果出栈的元素等于min栈顶,min也要出栈

//不过这个题目还有优化的空间,那就是[1,1,1,1,1]这样多个相同数的情况
//为避免空间浪费,可以设计一个count计数记录每个数,多次出现的数入、出栈只需要减计数
//计数归0才真正的出栈
class MinStack {
    
    
public:
    MinStack() 
    {
    
    }
    
    void push(int val) 
    {
    
    
        if(min.empty() || min.top() >= val)
        {
    
    
            min.push(val);
        }
        s.push(val);
    }
    
    void pop() 
    {
    
    
        if(s.top() == min.top())
        {
    
    
            min.pop();
        }
        s.pop();
    }
    
    int top() 
    {
    
    
        return s.top();
    }
    
    int getMin() 
    {
    
    
        return min.top();
    }
private:
    stack<int> s;
    stack<int> min;
};

Stack pop and push sequence

//这个题目最好的办法就是模拟栈的压入弹出过程
//比如pushv[1,2,3,4,5]这个序列得到popv[4,5,3,2,1]
//先入栈s,1,1 != popV.top(),继续入栈
//入栈s,2, 2 != popV.top(),继续入栈
//入栈s,3,3 != popV.top(),继续入栈
//入栈s,4, 4 == popV.top(),同时出栈,popV是一个数组,下标加1视为出栈
//出栈结束栈s.top() = 3 != popV.top() = 5,继续入栈
//入栈, 5, 5 == popV.top(),同时出栈
//出栈结束s.top() = 3 == popV.top() = 3,同时出栈
//………………………………………………………………………………
//最后pushV的所有元素都入栈,并且s栈刚好出空,说明pushV可以得到popV
//如果pushV所有元素入栈,s栈没法出空,说明pushV无法得到popV
class Solution {
    
    
public:
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) 
    {
    
    
        size_t pushi = 0;
        //popi下标加1视为popV出栈
        size_t popi = 0;
        stack<int> s;

        while (pushi < pushV.size()) 
        {
    
    
            s.push(pushV[pushi++]);
            while(!s.empty() && s.top() == popV[popi])
            {
    
    
                s.pop();
                popi++;
            }
        }

        return s.empty();
    }
};

Reverse Polish expression evaluation

//这个题目思路并不难,借助一个栈s即可
//(1)遇到数字:直接入s栈
//(2)遇到运算符,把栈中的两个左右数出栈,计算完把结果入s栈即可
//重复上面的步骤,一直到遍历完tokens即可,最后s栈顶即为结果
class Solution {
    
    
public:
    int evalRPN(vector<string>& tokens) 
    {
    
    
        stack<int> s;
        for(auto str : tokens)
        {
    
    
            if(!(str == "+" || str == "-" || str == "*" || str == "/"))
            {
    
    
               s.push(atoi(str.c_str()));
            }
            else
            {
    
    
                int right = s.top();  s.pop();
                int left = s.top();  s.pop();
                switch(str[0])
                {
    
    
                    case '+':
                        s.push(left + right);
                        break;
                    case '-':
                        s.push(left - right);
                        break;
                    case '*':
                        s.push(left * right);
                        break;
                    case '/':
                        s.push(left / right);
                        break;
                }
            }
        }
        return s.top();
    }
};

3.Introduction and use of queue

3.1 Introduction to queue

  1. queue is a queue.

  2. Queue is a container adapter, characterized by first-in-first-out , in which elements are inserted from one end of the container and elements are extracted from the other end.

  3. Queue is a class template (template <class T, class Container = deque<T> > class queue;). The underlying container can be one of the standard container class templates or other specially designed container classes. The underlying container should support at least the following operations:
    (1) empty: detect whether the queue is empty
    (2) size: return the number of valid elements in the queue
    (3) front: return a reference to the head element of the queue
    (4) back: return to the queue Reference to the tail element
    (5) push_back: put into the queue at the end of the queue
    (6) pop_front: dequeue at the head of the queue

  4. The standard container classes deque and list meet these requirements. By default, if no container class is specified for queue instantiation, the standard container deque is used.

  5. Use <queue> to include the header file.

Insert image description here

3.2Use of queue

Common interface description:

function illustrate
queue() Constructor, constructs an empty queue
empty() Determine whether the queue is empty, return true if empty, otherwise return false
size() Returns the number of elements in the queue
front() Returns a reference to the head element of the queue
back() Returns a reference to the last element of the queue
push(val) Enter element val at the end of the queue
pop() The head element of the team is dequeued

Exercise:
Implementing a stack using a queue

//这个题目的思路是有两个队列实现栈(其实也可以单队列实现,这里主要不是讲题目)
//需要始终有一个队列为空,比如[1,2,3]这个顺序队列
//队列q1为1 -> 2 - > 3,q2为空,top()只需要返回不为空的队列的队尾即可
//难点在出栈,出栈的话把1 -> 2转移到另一个队列q2,剩余一个元素即为栈顶,保存了再出栈即可
class MyStack {
    
    
public:
    MyStack() {
    
    }
    
    void push(int x) {
    
    
        if(q2.empty())
            q1.push(x);
        else
            q2.push(x);
    }
    
    int pop() {
    
    
        //默认q1为空栈
        queue<int>* empty_q = &q1;
        queue<int>* non_empty_q = &q2;
        if(q2.empty())
        {
    
    
            empty_q = &q2;
            non_empty_q = &q1;
        }
        while(non_empty_q->size() > 1)
        {
    
    
            empty_q->push(non_empty_q->front());
            non_empty_q->pop();
        }
        int ret = non_empty_q->front();
        non_empty_q->pop();
        return ret;
    }
    
    int top() {
    
    
        if(q1.empty())
            return q2.back();
        else
            return q1.back();
    }
    
    bool empty() {
    
    
        return q1.empty() && q2.empty();
    }
    queue<int> q1;
    queue<int> q2;
};

4. Introduction to functors

If you need to use it later, let me briefly introduce it first, and then explain the various uses in detail.

//仿函数,又称函数对象,其实就是一个类,重载了operator()
// 使得类对象可以像函数一样使用
// 相比传函数指针,传仿函数要更加灵活一些
//开一个命名空间,和库里面的区分开
namespace my_compare
{
    
    
	template<class T>
	class less
	{
    
    
	public:
		bool operator()(const T& x, const T& y)const
		{
    
    
			return x < y;
		}
	};

	template<class T>
	class  greater
	{
    
    
	public:
		bool operator()(const T& x, const T& y) 
		{
    
    
			return x > y;
		}
	};
}

5.Introduction and use of priority_queue

5.1Introduction to priority_queue

  1. priority_queue (priority queue) is a heap.

  2. A priority queue is a container adapter whose first element is always the largest of the elements it contains, according to strict weak ordering criteria.

  3. Elements can be inserted at any time in the priority queue, and only the largest (small) element (the element at the top of the priority queue) can be retrieved .

  4. The underlying container can be any standard container class template or other specifically designed container class. The container should support random access (subscript plus []), and support the following operations:
    (1) empty(): detect whether the container is empty
    (2) size(): return the number of valid elements in the container
    (3) front() : Return a reference to the first element in the container
    (4) push_back(): Insert an element at the end of the container
    (5) pop_back(): Delete the element at the end of the container

  5. The standard container classes vector and deque meet these needs. By default, vector is used if no container class is specified for a particular priority_queue class instantiation.

  6. Random access needs to be supported so that the heap structure is always maintained internally.

  7. priority_queue是一个模板类,template <class T, class Container = vector<T>, class Compare = less< typename Container::value_type>> class priority_queue;

  8. Include the <queue> header file before use

5.2Usage of priority_queue

Common interface description:

function illustrate
priority_queue() Construct an empty priority queue
priority_queue(begin(), end()) Pass iterator range initialization
empty() Determine whether the priority queue is empty, return true if empty, otherwise return false
top() Returns the largest (small) element in the priority queue, that is, the top element of the heap
push(val) Insert element val in priority queue
pop() Delete the largest (small) element in the priority queue, that is, the top element of the heap

Notice:

  1. By default priority_queue is a large heap.
#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;
}
  1. If you put custom type data in priority_queue, the user needs to provide an overload of > or < in the custom type.

practise:

The Kth largest element in the array

//对于取数组中前K个最大的元素(TOPK),很容易就能想到利用堆
//先把原数组构造一个堆,要求第K大的元素
//只需要pop() K - 1次即可,最后堆顶一定是第k个大的元素
//其中建堆的时间复杂度为O(N),K - 1次的删除后调整为 (K-1) * logN
//在 K比较小的情况下,时间复杂度接近于O(N)
class Solution {
    
    
public:
    int findKthLargest(vector<int>& nums, int k)
    {
    
    
        //其实这个题目也可以利用快速排序,不过必须选择性的排序区间
        priority_queue<int> p(nums.begin(), nums.end());
        for(int i = 0; i < k - 1; i++)
        {
    
    
            p.pop();
        }
        return p.top();
    }
};

6.Introduction to deque

deque (double-ended queue): It is a double-opening " continuous " space data structure. The meaning of double-opening is that insertion and deletion operations can be performed at both ends , and the time complexity is O(1), which is the same as Compared with vector, header insertion is more efficient and does not require moving elements; compared with list, space utilization is relatively high.

6.1deque implementation principle

Deque is not a truly continuous space , but is made up of continuous small spaces . The actual deque is similar to a dynamic two-dimensional array. Its underlying structure is as shown in the figure below:

Insert image description here

6.2Deque defects

  1. It is not suitable for traversal , because when traversing, the iterator of deque needs to frequently detect whether it moves to the boundary of a certain small space, resulting in low efficiency. In sequential scenarios, frequent traversal may be required, so in practice, it is necessary When it comes to linear structures, vector and list are preferred in most cases . There are not many applications of deque, and one application that can be seen so far is that STL uses it as the underlying data structure of stack and queue .
    (Include the header file <deque> if you really want to use it)
  2. Head-to-tail insertion is highly efficient, but intermediate insertion still requires moving data (the length of the subarray is constant), and frequent subscript plus [] access consumes a lot of calculations. At this time, the efficiency is far lower than that of vector and list.

6.3 Reasons for choosing deque as the underlying default container for stack and queue

  1. Stack and queue do not need to be traversed (so stack and queue do not have iterators), they only need to operate on one or both fixed ends.
  2. When the elements in the stack grow, deque is more efficient than vector ( no data needs to be moved during expansion ); when the elements in queue grow, deque is not only efficient, but also has high memory usage .

7. Simulation implementation

7.1Stack simulation implementation

namespace my_std
{
    
    
	template<class T, class container = deque<T>>
	class stack
	{
    
    
	public:
		void push(const T& x)
		{
    
    
			_con.push_back(x);
		}

		void pop()
		{
    
    
			_con.pop_back();
		}

		T& top()
		{
    
    
			return _con.back();
		}

		size_t size()
		{
    
    
			return _con.size();
		}

		bool empty()
		{
    
    
			return _con.empty();
		}

	private:
		container _con;
	};
}

7.2Simulation implementation of queue

namespace my_std
{
    
    
	template<class T, class container = deque<T>>
	class queue
	{
    
    
	public:
		void push(const T& x)
		{
    
    
			_con.push_back(x);
		}

		void pop()
		{
    
    
			_con.pop_front();
		}

		T& front()
		{
    
    
			return _con.front();
		}

		T& back()
		{
    
    
			return _con.back();
		}

		size_t size()
		{
    
    
			return _con.size();
		}

		bool empty()
		{
    
    
			return _con.empty();
		}

	private:
		container _con;
	};
}

7.3 Simulation implementation of priority_queue

This article does not mainly talk about heap-related algorithms. If you have questions about heap construction and adjustment, you can read previous heap explanations:
Heap implementation
, heap practical application and time complexity analysis

namespace my_std
{
    
    
	//优先级队列
	template<class T, class container = vector<T>, class comparation = less<T>>
	class priority_queue
	{
    
    
	public:
		//向上调整
		void adjust_up(size_t child)
		{
    
    
			comparation com; //用于比较的仿函数
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
    
    
				// _con[parent]  < _con[child]
				//默认大堆,如果孩子大,就交换和父亲的值
				//然后更新孩子和父亲的下标
				if (com(_con[parent], _con[child]))
				{
    
    
					std::swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
    
    
					break;
				}
			}
		}

		void push(const T& x)
		{
    
    
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}

		//向下调整
		void adjust_down(size_t parent)
		{
    
    
			size_t child = parent * 2 + 1;
			comparation com;
			while (child < _con.size())
			{
    
    
				//_con[child] < _con[child + 1]
				//默认左孩子大,如果右孩子大就让孩子下标加1,注意右孩子不存在的情况
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
    
    
					child++;
				}
				//_con[parent] < _con[child]
				//默认大堆。如果孩子大就和父亲交换,然后更新孩子下标和父亲下标
				if (com(_con[parent], _con[child]))
				{
    
    
					std::swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;	 
				}
				else
				{
    
    
					break;
				}
			}
		}

		void pop()
		{
    
    
			std::swap(_con.front(), _con.back());
			_con.pop_back();
			adjust_down(0);
		}


		//无参构造,这个必须写,不然就没有默认构造用了
		priority_queue()
		{
    
    }

		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--)
			{
    
    
				adjust_down(i);
			}
		}

		
		/// ///
		
		T& top()
		{
    
    
			return _con.front();
		}

		size_t size()
		{
    
    
			return _con.size();
		}

		bool empty()
		{
    
    
			return _con.empty();
		}
	private:
		container _con;
	};
}

Guess you like

Origin blog.csdn.net/2301_76269963/article/details/132788066