C++-stack and queue

 Introduction to stack and queue

 The two are the stack and the queue in C++, but in C++, the two are not implemented in traditional ways, such as sequential storage and chained storage, and the two are now implemented using a permission method ;

They are all implemented with container adapters. The developers consider that there is no need to implement them in the same way as the traditional writing method, and they can be implemented directly by reusing the container. As shown in the official document in the figure below, both are implemented based on the deque container. :

 The essence of the adapter is actually multiplexing .

 The functions implemented by the two are actually no different from what we learned in the data structure, and the structure is the same;

 Both are container adapters, which do not support casual traversal and have their own rules for insertion and deletion, so these two containers do not support iterators.

 Stack official documentation: stack - C++ Reference (cplusplus.com)

 Queue official documentation: queue - C++ Reference (cplusplus.com)

Examples of stack and queue

 Because the classes in STL are similar in use, and the use of the two is very simple, so directly use a few examples to understand the use of the stack and queue classes.

155. Minimal Stack - LeetCode

Design a stack that supports  push the , pop , top operations, and can retrieve the smallest element in constant time.

Implementation  MinStack class:

  • MinStack() Initialize the stack object.
  • void push(int val) Push the element val onto the stack.
  • void pop() Removes the element at the top of the stack.
  • int top() Get the element at the top of the stack.
  • int getMin() Get the smallest element in the stack.

 The problem is that the geiMin() function requires a time complexity of O(1). So it cannot be solved in a conventional way, and the stack does not support traversal.

First of all, what we think of is that there is a stack container in the MinStack class, and there is a Min member, which is used to store the minimum value in the stack container. When a value smaller than Min is pushed, Min is updated. This has a lot of problems, it is difficult to update Min in time when Pop is needed.

So the above method is not feasible.

To solve this problem, we use two stacks to trade space for time. One stack stores the elements that are pushed in, that is, the stack that stores data; the other stack stores the minimum value of the first stack, but the storage method is slightly different:

 As shown in the figure above, the _st stack is the stack for storing data. The order of push in the above example is (3 4 5 1). When _st is pushed into the stack 3, at this time 3 is the minimum value of the _st stack, then push 3 in _minst; then when _st is pushed 4, the minimum value of the _st stack is still 3 at this time, then_ The minst stack is still push 3; the next 5 is the same; when _st is pushed into the stack 1, 1 becomes the minimum value of the _st stack, then the _minst stack is pushed 1.

Use the above method to record when there is no push into the _st stack, what is the minimum value in the _st stack at this time.

The above code can also be optimized. There are multiple 3s stored in the above _minst stack. In fact, these repeated values ​​do not need to be deleted, and only need to push smaller elements into the stack. As shown below:

Note : In the above optimized version, if the element pushed into the stack in _st is equal to the top element of _minst, it will also be pushed into the stack.

It is not necessary to write a constructor, but it is also possible to write an empty constructor, because if the constructor is implemented, even if there is no operation in it, the compiler will call the initialization list, and the initialization list defaults to not processing built-in types. For a custom type, the constructor of this custom type will be called. Therefore, it is ok not to write this constructor; it is also ok to write an empty constructor without any operation.

Similarly, the destructor is not necessary to write. Here, the compiler will automatically generate it to process the class minstack, and the two stack members, stack has its own destructor to process, so in this class, The destructor is not written.

Code:

class MinStack {
public:
    // 可写可不写
    MinStack() {
    }
    
    void push(int val) {
        _st.push(val);

        if(_minst.empty() || _minst.top() >= val)
        {
            _minst.push(val);
        }
    }
    
    void pop() {
        if(_minst.top() == _st.top())
        {
            _minst.pop();
        }

        _st.pop();
    }
    
    int top() {
        return _st.top();
    }
    
    int getMin() {
        return _minst.top();
    }

private:
    stack<int> _st;
    stack<int> _minst;
};

 Stack push and pop sequence

Input two integer sequences, the first sequence represents the push order of the stack, please judge whether the second sequence may be the pop order of the stack. Assume that all numbers pushed onto the stack are unequal. For example, the sequence 1, 2, 3, 4, 5 is the push sequence of a certain stack, and the sequence 4, 5, 3, 2, 1 is a pop sequence corresponding to the push sequence, but 4, 3, 5, 1, 2 It cannot be the pop sequence of the push sequence.

1. 0<=pushV.length == popV.length <=1000

2. -1000<=pushV[i]<=1000

3. All numbers of pushV are different

 It can be simulated to judge whether the order of pushing and popping is the same. Analyze the following example: 

First of all, this brother example satisfies the conditions, so first judge from the first one in the stacking order, and gradually push it into the stack. If 1 and 3 are not equal, then push to the stack. If 2 and 3 are not equal, continue to push to the stack. If 3 and 3 are equal, pop the stack after pushing 3, and then judge the sequence of popping 2, 2 and 2 are equal, and continue to pop the stack ; Next, judge that 5 in the stacking sequence is not equal, and continue to push into the stack; after 5 in the stacking sequence is equal to 5 in the stacking sequence, it is popped; then 4 and 4 are equal, and then popped, Finally, the push and pop are completed.

If it is an example that does not meet the conditions, according to the above process, after the stacking is completed, the popping must not be completed, which means that the conditions are not met.

Code:

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pushV int整型vector 
     * @param popV int整型vector 
     * @return bool布尔型
     */
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
        stack<int> st;
        int pushi = 0, popi = 0;

        while(pushi < pushV.size())
        {
            st.push(pushV[pushi++]);

            if(st.top() != popV[popi])
            {
                continue;
            }
            else 
            {
                while(!st.empty() && st.top() == popV[popi])
                {
                    st.pop();
                    ++popi;
                }
            }
        }

        return st.empty();
    }
};

150. Reverse Polish Expression Evaluation - LeetCode 

You are given an array of strings  tokens representing an   arithmetic expression represented according to Reverse Polish Notation .

Please calculate this expression. Returns an integer representing the value of the expression.

 The expressions we usually use are all infix expressions, such as: 1 + 2 * 3, the infix expression means that the operator is in the middle of the operand, but this is not conducive to the computer to recognize the priority, and the computer recognizes is the postfix expression used, which is the reverse Polish expression mentioned above.

He is that the order of the operands remains unchanged, and the order of the operators is arranged according to the priority. The operands come first, and the operators follow, such as: 2 + 1 * 3, written as a suffix expression is 2 1 3 * +  

The above topic requires only to calculate the result of the given suffix expression, there is no requirement to convert the infix to the suffix, then it is very simple to just calculate the result of the suffix expression.

A stack can be used to store the results. First, let all the operands be pushed onto the stack, and then take the two elements at the top of the stack for operation, and push the result onto the stack. This allows the result of the postfix expression to be computed.

Code:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for(auto& ch : tokens)
        {
            if(ch == "+" || ch == "-" || ch == "*" || ch == "/")
            {
                int right = st.top();
                st.pop();

                int left = st.top();
                st.pop();

                switch(ch[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(ch));
            }
        }

        return st.top();
    }
};

For infix to suffix, here is a simple explanation of the idea: still use a stack, traverse the expression once, if it encounters an operand, it will output it directly, if it encounters an operator, it will push it into the stack.

For operator stack operations, there are two cases:

  • If the stack is empty or the current operator has a higher priority than the top of the stack, it will continue to be pushed onto the stack.
  • If the stack is not empty or the priority of the current operator is lower than or equal to that of the top of the stack, then output the operator on the top of the stack

 Then this involves the issue of operator priority, which can be solved if map is used to establish the corresponding mapping relationship. If you don't use map, you can use if else or switch to implement it violently.

102. Level order traversal of binary tree - LeetCode 

 Given the root node of your binary tree  , return a level-order traversalroot  of its node values   . (ie layer by layer, visit all nodes from left to right).

This topic is different from ordinary layer sequence traversal. It also needs to assign the node values ​​of each layer of the tree to each layer of a two-dimensional array. Therefore, we not only need to print, but also determine the currently printed node. It's that layer.

Method 1: Two queues can be used, one queue is used for layer sequence traversal, enqueue and dequeue tree nodes; the other is for storing the number of layers that the currently enqueued node belongs to. As shown below;

 Although the above-mentioned double queue can be realized, it is not the optimal solution.

Even better is to use a levelsize variable to control the output nodes of each layer:

The specific method is that before the first node of a certain layer, first calculate the number of nodes in this layer and save it in levelsize; then when we output a node of a node in this layer, levelsize-- , until the levelsize decreases to 0, at this time, it means that the nodes of this layer have been output. As shown in the figure below (before the output of the third layer):

 

We can control the output of each layer in this way, but the question is how do we calculate the number in each layer?

In fact, in the process of entering and leaving the team, until the traversal of the tree is complete, it is a cycle to the end; then in fact, a layer of loops can be added to this layer of loops, and this layer of loops is used to output the results of this layer. point, when the inner loop is finished, the nodes of the current layer have been dequeued, but all the children of this layer have been enqueued, and all the nodes in the queue at this time are the parent nodes of the previous layer. The child nodes given by the point, that is to say, all the nodes stored in the queue at this time are all the nodes of the next layer.

Therefore, we can directly find the number of elements in the current queue and assign it to levelsize.

Code:
 

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())
        {
            vector<int> v;
            for(int i = 0; i < levelsize;i++)
            {
                TreeNode* front = q.front();
                q.pop();
                v.push_back(front->val);

                if(front->left)
                {
                    q.push(front->left);
                }

                if(front->right)
                {
                    q.push(front->right);
                }
            }
            
            vv.push_back(v);
            levelsize = q.size();
        }

        return vv;
    }
};

102. Level order traversal of binary tree - LeetCode

Given the root node of your binary tree  , return a level-order traversalroot  of its node values   . (ie layer by layer, visit all nodes from left to right).

Use the above method, and then reserve the class used to store the vv vector.

Guess you like

Origin blog.csdn.net/chihiro1122/article/details/131926053