Container adapter designed by STL, plus analysis of classic topics

content

foreword

stack + queue container adapter understanding implementation

Understanding container adapters

Think about whether stack and queue have iterators?

Which containers can stack and queue use as the underlying container? 

How is the underlying container passed in?     

 Key function block analysis

stack analysis 

 queue analysis

stack classic example

priority_queue decomposition implementation

Understanding priority_queue from the data structure

View priority_queue from the perspective of STL components

Key analysis of push and pop functions

AdjustUp

AdjustDown

Overall implementation code

priority_queue Applicable TopK problem analysis

Summarize


foreword

Xiaojie's STL design essays will continue to be updated. There are vector and list essays with Xindi before. Attach the link here, you can read it if you are interested, and Xiaojie will continue to output good works based on what he has learned. I hope everyone You can follow, communicate, thank you, your comments, reading, and comments are the greatest recognition and support for Xiaojiedi

Block analysis from function prototype to block implementation of C++ STL (vector) .csdn.net/weixin_53695360/article/details/123248476?spm=1001.2014.3001.5502 Linked list design of STL design , block and component analysis, iterator design ideas Block component analysis, iterator design ideas https://blog.csdn.net/weixin_53695360/article/details/123647344?spm=1001.2014.3001.5502

stack + queue container adapter understanding implementation

  • Understanding container adapters

Taking a certain existing container as the underlying structure, the interface function is further encapsulated on its basis. Make it FIFO (stack feature) or FIFO (queue feature)   

The above-mentioned features are based on the underlying container, encapsulate a new interface, and form a container with another characteristic. This is often not called a container, but an     adapter .

That's why stack + queue in STL is often not classified as container (container) but container adapter container adapter   ( container adapter ) 

  • Think about whether stack and queue have iterators?

Neither stack nor queue have iterators, because neither has the function of traversing and visiting, so naturally there is no need to design iterators

  • Which containers can stack and queue use as the underlying container? 

It is OK to use deque double-ended queue + list doubly circular linked list as the underlying container. It is also OK to use vector as the underlying container for stack.

  • How is the underlying container passed in?     

Pass it in as a template parameter. The following way

 

  •  Key function block analysis

  • stack analysis 

  • Because this stack is just an adapter, it is equivalent to a layer of encapsulation based on the interface of the existing container. Because it is re-encapsulation, the function analysis only floats on the function prototype. As long as we re-encapsulate according to the characteristics of the stack, it is       (Note: stack and queue grabs are features, not implementations, stacks, first-in, first-out, bullets are fired first)
  • Therefore, we only need to push data and pop data on one interface to meet the characteristics of the stack. It is possible to select the head or the tail. Here, the vector is used as the underlying container, so I use the back position to push the elements. in and out  
namespace tyj {
	//template<class T, class Sequence = deque<T>>
	//template<class T, class Sequence = list<T>>
	template<class T, class Sequence = vector<T>>
	class stack {
	public:
		stack() {}//无参构造
		bool empty() const {
			return st.empty();
		}

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

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

		void push(const T& val) {
			st.push_back(val);
		}

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

	private:
		Sequence st;
	};
}
  •  queue analysis

  •  The principle of queue and stack is exactly the same. It only needs to meet the first-in-first-out feature. To meet this feature is queue, that is, elements are output at one end of the container and elements are input at the other end. Such a structure is a queue.
  • For the queue, it is necessary to operate at both ends, one end enters the element, and the other end outputs the element, so we need a practical double-end operation container as the underlying container, which can be a deque or a list double-ended linked list. Here we use list as the bottom layer. The container further encapsulates the interface
namespace tyj {
    template<class T, class Sequence = list<T>>
	class queue {
	public:
		queue() {} //无参构造
		bool empty() const {
			return q.empty();
		}

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

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

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

		void push(T& val) {
			q.push_back(val);
		}

		void pop() {
			q.pop_front();
		}
	private:
		Sequence q;
	};
}

stack classic example

The sword refers to Offer 31. The push and pop sequence of the stack

Input: pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
Output: true
Explanation: We can execute in the following order:
push(1), push(2) , push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1

The meaning of the title is to give a pushed array as the stacking sequence, and the poped array is the stacking sequence. According to the stacking sequence of the pushed array, is the given stacking array popping sequence a possible result?

  • Idea description: For this possible stack pop sequence problem, it should be handled by stack simulation, because it is very complicated and difficult to solve this problem directly from the overall thinking. At this time, we can consider the It is really put into the actual stack according to the push and pop sequence, and simulate this process to see if such a push sequence may produce such a pop sequence.
  • First, pushI is the subscript pointing to the pushed array, and popJ is the subscript pointing to the poped array.
  • We continue to push the element pointed to by pushI into the st stack. If st.top == poped[popJ] occurs, it means that we should pop this element
  • We constantly put the elements in the pushed array into the st stack, and at the same time we constantly compare whether the st.top() element is the current popped element. After the sequence is completed, we can judge whether popJ has reached the end of the poped array. Going to the end indicates that it is a legal pop-up sequence, otherwise it is not
class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int> st;
        int pushI = 0, popJ = 0;
        for (pushI = 0; pushI < pushed.size(); ++pushI) {
            st.push(pushed[pushI]);
            while (popJ < popped.size() && !st.empty() && popped[popJ] == st.top()) {
                st.pop();
                ++popJ;
            }
        }
        return popJ == popped.size();                  
    }
};

At the core of the above problem, we actually put the sequence elements into an actual stack to simulate the whole process. During the stacking process, we greedily try to compare the top elements of the pop stack. If the whole process is finished, popJ goes to the poped place. The end is a valid pop sequence, otherwise it would not be. 

150. Reverse Polish Expression Evaluation

Input: tokens = ["2","1","+","3","*"] 
Output: 9 
Explanation: This expression is converted into a common infix arithmetic expression: ((2 + 1) * 3) = 9

The meaning of the title: The title is given an array of suffix expressions, and then we need to convert it to our usual visible infix expressions to find the answer of the infix expressions

  • Postfix expression lhs left operand rhs right operand op operator 
  • We need to convert it to the form lhs op rhs to handle
  • Therefore, in the process of traversing the ground, we need to push lhs to the stack first, and rhs to the stack. When we encounter op, we will pop the two top elements of the stack, and push the operation result to the stack at the same time.
  • The final return to st.top() is the final ans
class Solution {
public:
    int evalRPN(vector<string>& s) {
        stack<int> st;
        int lhs, rhs;
        for (int i = 0; i < s.size(); ++i) {
          if (s[i] == "+") {
              rhs = st.top(); st.pop();
              lhs = st.top(); st.pop();
              st.push(lhs + rhs);
          } else if (s[i] == "-") {
              rhs = st.top(); st.pop();
              lhs = st.top(); st.pop();
              st.push(lhs - rhs);
          } else if (s[i] == "*") {
              rhs = st.top(); st.pop();
              lhs = st.top(); st.pop();
              st.push(lhs * rhs);
          } else if (s[i] == "/") {
              rhs = st.top(); st.pop();
              lhs = st.top(); st.pop();
              st.push(lhs / rhs);
          } else {
              st.push(stoi(s[i]));
          }
        }
        return st.top();
    }
};

priority_queue decomposition implementation

Understanding priority_queue from the data structure

  • First understand what a priority queue is. The priority queue is a queue with weights, which is stored according to the size of the weights. In fact, it is the heap data structure that we touch in the C language. Priority queue "==" heap
  • The weight of the parent node needs to be greater than the two child nodes

View priority_queue from the perspective of STL components

  •  priority_queue is essentially a container adapter like queue, and the underlying container is practically vector

  • what is compare? It is a functor, a callable object, and a comparison rule:   it can be understood as a function pointer in the C language

Key analysis of push and pop functions

  • The core key is to understand why it is necessary to float and sink ( the example here is a small top heap )
  • Push elements to the end of the array and the bottom of the heap, which may affect the characteristics of the entire heap structure. You need to call the AdjustUp upward adjustment algorithm to adjust the inserted elements to the correct position.
 
  
  • The pop element is the top element of the heap. The method is to use the heap tail element and the top element to swap, and then perform pop_back() to pop up the entire element that should be at the top of the heap, and then the replacement of the top element will affect the entire heap. The characteristics of the structure make it not meet the characteristics requirements of the heap structure, so it is necessary to call the downward adjustment algorithm to operate the bottom element of the heap to the position where it should be.

  • So far, the idea is very clear. What is the key point? It is the adjustment algorithm of AdjustUp and AdjustDown upward and downward.
  • AdjustUp

  • AdjustDown

Overall implementation code

namespace tyj {	
    template<class T>
	struct less
	{
		bool operator()(const T& l, const T& r)
		{
			return l < r;
		}
	};

	template<class T>
	struct greater
	{
		bool operator()(const T& l, const T& r)
		{
			return l > r;
		}
	};

	//优先队列
	template<class T, class Sequence = vector<T>, class Compare = less<T> >
	class priority_queue {
	public:
		priority_queue() {
			h.push_back(T());//对于ind == 0 我们插入一个占位,我的习惯
		}
		bool empty() const {
			return h.size() == 1;
		}

		T& top() {
			return h[1];
		}

		void AdjustUp(size_t child) {
			Compare cmp;
			size_t fa = child >> 1;//fa ind
			while (fa) {//fa >= 1
				if (cmp(h[fa], h[child])) {
					//说明child 应该上浮操作
					swap(h[fa], h[child]);
					child = fa;
					fa = child >> 1;//继续向下
				}
				else {
					break;
				}
			}
		}
		void AdjustDown(size_t fa) {
			Compare cmp;
			size_t child = fa << 1, n = size();
			while (child <= n) {
				size_t l = child, r = l | 1;
				if (r <= n && cmp(h[l], h[r])) {
					++child;
				}
				if (cmp(h[fa], h[child])) {
					swap(h[fa], h[child]);
					fa = child;
					child = pa << 1;
				}
				else {
					break;//说明不能再下沉了
				}
			}
		}

		size_t size() {
			return h.size() - 1;
		}

		void pop() {
			//交换元素
			swap(h[1], h[size()]);//至此排除数组最后末尾元素
			h.pop_back();//弹掉堆顶
			AdjustDown(1);//从上往下做调整
		}
		void push(const T& val) {
			h.push_back(val);//插入val;
			//然后进行上浮 操作
			AdjustUp(size());
		}
	private:
		Sequence h;
	};
}

priority_queue Applicable TopK problem analysis

The sword refers to Offer II 076. The kth largest number in the array

The meaning of the title is very simple, that is, how to get the kth earth number in the array

  • Idea: Use the root heap, first put all the elements into the heap, and then the top of the heap is the first largest element, and then we keep popping the top of the heap, and when it pops to the Kth place, it is the Kth largest element.
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> q;//默认大根堆
        for (int e : nums) {
            q.push(e);
        }
        int cnt = 1;//cnt代表第几大
        while (1) {
            if (cnt == k) return q.top();
            q.pop();
            cnt++;//弹出一个cnt ++ 
        }
    }
};

Interview question 17.14. Minimum number of K

The meaning of the title is very simple, that is, how to get the minimum number of K in the array

  • For this question, we need to use a large root heap, which subverts the cognition of many people, what? Why do we need to use a large root heap to obtain the minimum number of K?     
  • Idea 1: Like the above, maintaining the small root heap is the easiest. First put all of them into the heap, and then pop K out is the minimum number of K. In this way, the maintenance heap is relatively large, and you need to put all of them into the heap at first, and then pop them out.
  • Idea 2: We maintain a heap with size == k, but we maintain a large root heap, and each time the maximum value among the current K minimum values, which is the top of the heap, is eliminated, and the smaller value is pushed into the

  •  Core 1: Maintain a large top heap with K minimum values
  • Core 2: Why use a large top heap? The top of the heap is the one with the largest ground among the current K minimum values. The largest value means that it should be eliminated the most (  greedy elimination of large ground values, and push of smaller ground values   )
  • Eliminate the earth value, keep the minimum K values ​​in the heap, and finally obtain the K minimum values, and the size of the heap will never exceed K, and the complexity is lower than the first method
class Solution {
public:
    vector<int> smallestK(vector<int>& arr, int k) {
        vector<int> ans;
        ans.clear();
        if (k == 0) return ans;
        priority_queue<int> q;//默认大顶堆
        for (int e : arr) {
            if (q.size() < k) {
                q.push(e);
                continue;
            }
            if (e < q.top()) {
                q.pop();
                q.push(e);
            }
        }
        while (!q.empty()) {
            ans.push_back(q.top());
            q.pop();
        }
        return ans;
    }
};

Summarize

  • First of all, this article mainly starts with the container adapter and analyzes the queue + stack + priority_queue in STL
  • Container adapters are essentially other containers with certain characteristics that are further encapsulated from existing containers
  • The essence of stack + queue lies in its nature, stack FIFO, single opening, queue FIFO, double opening, one opening enters, the other opening
  • priority_queue is a queue with weights. In fact, the bottom layer is the heap. Using the heap can quickly solve the Top K problem.
  • To obtain the value of the K-th largest and K-th smallest, the corresponding large-top heap or small-top heap is directly used.
  • However, to obtain the largest or smallest K values, we use the small top heap and the large top heap instead. We can use the small top heap to quickly obtain the smallest of the current relatively large K values, and the smallest one is positive. is the one that is most easily eliminated, so we will eliminate the smaller one and push the other larger ones
  • The core of obtaining the largest and smallest K elements is to maintain a heap: this heap has only K elements, and the K elements are the largest or smallest K. . .       Maintenance means that values ​​that don't belong in it need to be eliminated, so we maintain the opposite heap for elimination

Guess you like

Origin blog.csdn.net/weixin_53695360/article/details/123769997