[C++系列] 59. stack介绍使用及相关OJ、二叉树三序遍历、LCA问题

1. stack的介绍和使用

1.1 stack的介绍

1.stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
2. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
3. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:

  • empty:判空操作
  • back:获取尾部元素操作
  • push_back:尾部插入元素操作
  • pop_back:尾部删除元素操作

4.标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,
默认情况下使用deque。

1.2 stack的使用

函数说明 接口说明
stack(const container_type& ctnr = container_type()) 构造空的栈
bool empty() const 检测stack是否为空
size_type size() const 返回stack中元素的个数
value_type& top() 返回栈顶元素的引用
const value_type& top() const 返回栈顶元素的const引用
void push (const value_type& val) 将元素val压入stack中
void pop() 将stack中尾部的元素弹出
template <class… Args> void emplace (Args&&… args) (C++11) 在stack的栈顶构造元素
void swap (stack& x) (C++11) 交换两个栈中的元素

1.3 stack在OJ上的使用

1.3.1 最小栈
class MinStack {
public:
    /** initialize your data structure here. */
   	// 构造函数
    MinStack() {
        
    }
    
    void push(int x) {
        // 压栈操作,统一将元素保存在s中
        s.push(x);
        // 如果x小于s_min栈顶的元素,将x再压如s_min栈中
        if (s_min.empty() || x <= s_min.top())
            s_min.push(x);
    }
    
    void pop() {
        // 如果s_min栈顶元素等于出栈元素,s_min栈顶元素移除
        if (s_min.top() == s.top())
            s_min.pop();
        s.pop();
    }
    
    int top() {
        return s.top();
    }
    
    int getMin() {
        return s_min.top();
    }

private:
    // 保存最小栈元素
    std::stack<int> s;
    // 保存栈的最小值
    std::stack<int> s_min;
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(x);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

这是一道十分常见的stack应用题,也是面试过程中常考的问题。需要用两个栈来进行操作,第一个栈 s 用以保存数据,第二个栈s_min为最小栈。
push接口:首先元素全部入栈s,当s_min为空栈或者其栈顶元素比x元素还大时,最小栈入栈元素x。
pop接口:如果最小栈元素与要出栈元素相等时,需要将最小栈元素进行出栈操作,s栈栈顶元素无条件pop。
top接口:返回s栈栈顶元素即可。
getMin接口:返回最小栈顶元素即可。

1.3.2 两个队列实现一个栈
class queueStack
{
	queue<int> m_qu1;
public:
	queueStack()
	{

	}

	void push(int x)
	{
		m_qu1.push(x);
	}

	void pop()
	{
		queue<int> m_qu2;
		while (m_qu1.size() > 1)
		{
			m_qu2.push(m_qu1.front());
			m_qu1.pop();
		}
		m_qu1 = m_qu2;
	}

	int top()
	{
		queue<int> m_qu2;
		while (m_qu1.size() > 1)
		{
			m_qu2.push(m_qu1.front());
			m_qu1.pop();
		}
		int tmp = m_qu1.front();
		m_qu2.push(m_qu1.front());
		m_qu1 = m_qu2;
		return tmp;
	}
};
1.3.3 用两个栈实现队列
class MyQueue {
private:
    stack<int> s1,s2;
public:
    /** Initialize your data structure here. */
    MyQueue() {
        
    }
    
    /** Push element x to the back of queue. */
    void push(int x) {
        while(!s2.empty())
        {
            int temp=s2.top();
            s2.pop();
            s1.push(temp);
        }
        s1.push(x);
    }
    
    /** Removes the element from in front of queue and returns that element. */
    int pop() {
        int res=peek();
        s2.pop();
        return res;
    }
    
    /** Get the front element. */
    int peek() {
        while(!s1.empty())
        {
            int temp=s1.top();
            s1.pop();
            s2.push(temp);
        }
        return s2.top();
    }
    
    /** Returns whether the queue is empty. */
    bool empty() {
        return s1.empty()&&s2.empty();
    }
};
1.3.4 栈的压入弹出序列
class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        // 入栈元素与出栈元素个数必须相同
        if (pushV.size() != popV.size())
            return false;
        // 用s来模拟入栈及出栈过程
        int out = 0;
        int in = 0;
        stack<int> s;
        while (out < popV.size()) {
            // 如果s为空,或者栈顶元素与出栈元素不相等,就入栈
            while (s.empty() || s.top() != popV[out]) {
                if (in < pushV.size())
                    s.push(pushV[in++]);
                else 
                    return false;
            }
            // 栈顶元素与出栈元素相等,出栈
            s.pop();
            out++;
        }
        return true;
    }
};

这个题目在许多考试中很喜欢以选择题的形式出现,也比较好容易判断,思考过程就是模拟一遍即可,在此编程也不需要费太多心思找其数字规律,先采用辅助栈操作,进行模拟即可。

1.3.5 逆波兰表达式求值
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> tmp;
        int a, b;

        for (auto & vsi : tokens)
        {
            if (isdigit(vsi[0]) || vsi.size() > 1)
            {
                tmp.push(atoi(vsi.c_str()));
            }
            else
            {
                a = tmp.top();
                tmp.pop();
                b = tmp.top();

                switch (vsi[0])
                {
                case '+':
                    tmp.top() = b + a;
                    break;
                case '-':
                    tmp.top() = b - a;
                    break;
                case '*':
                    tmp.top() = b * a;
                    break;
                case '/':
                    tmp.top() = b / a;
                    break;
                }
            }
        }
        return tmp.top();
    }
};

经典问题,也是选择题很常见:“给一个式子求该式的后缀”,即有(1+2)*3,计算机在对该式进行处理的时候会将其处理为二叉树的形式:
在这里插入图片描述
并对其进行后序遍历:12+3 *,这就是计算机处理的方式,但其实已知中序遍历,前序遍历求解二叉树的后续遍历形式,计算机会再通过逆波兰表达式将其求解得到答案即可。
在这里插入图片描述
在这里插入图片描述
遍历依次从左到右找符号,找到符号将该符号前两个数字与符号进行运算即可,以二叉树的后缀形式求解完毕。即在此先找+,9+3为12,在再找 *,12 * -11为-132,在依次进行完毕即可。
逆波兰表达式就是算式形式处理中的后缀形式处理,在编译原理中为基础知识。

1.3.6 \color{red}{1.3.6 二叉树的最近公共祖先}

方法一:后序在遍历节点时,栈内元素就是该节点到root节点中的全部节点,利用后序遍历的非递归特性,求解:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
		TreeNode* cur = root;
		stack<TreeNode*> st;
		stack<bool> tag;

		stack<TreeNode*> res1;
		stack<TreeNode*> res2;

		while (cur || !st.empty())
		{
			for (; cur; cur = cur->left)
			{
				st.push(cur);
				tag.push(false);
			}

			while (!st.empty() && tag.top())
			{
				cur = st.top();
				if (cur == p || cur == q)
				{
					if (res1.empty())
					{
						res1 = st;
					}
					else
					{
						res2 = st;
					}
				}
				st.pop();
				tag.pop();

				cur = nullptr;
			}

			if (!st.empty())
			{
				tag.top() = true;
				cur = st.top();
				cur = cur->right;
			}
		}

		if (res1.size() < res2.size())
		{
			swap(res1, res2);
		}

		int i = res1.size() - res2.size();

		for (; i > 0; i--)
		{
			res1.pop();
		}

		while (res1.top() != res2.top())
		{
			res1.pop();
			res2.pop();
		}

		return res1.top();
	}
		/*TreeNode * cur = root;
		stack<TreeNode*> st;
		TreeNode* tmp = nullptr;
		size_t stsize = 0;

		while (cur || !st.empty())
		{
			for (; cur; cur = cur->left)
			{
				st.push(cur);
			}

			if (!st.empty())
			{
				cur = st.top();

				if (stsize > st.size())
				{
					tmp = cur;
					stsize = st.size();
				}

				if (cur == p || cur == q)
				{
					if (!tmp)
					{
						tmp = cur;
						stsize = st.size();
					}
					else
					{
						return tmp;
					}
				}
				st.pop();
				cur = cur->right;
			}
		}
		return nullptr;
    }
    */
};

方法二:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
		TreeNode * cur = root;
		stack<TreeNode*> st;
		TreeNode* tmp = nullptr;
		size_t stsize = 0;

		while (cur || !st.empty())
		{
			for (; cur; cur = cur->left)
			{
				st.push(cur);
			}

			if (!st.empty())
			{
				cur = st.top();

				if (stsize > st.size())
				{
					tmp = cur;
					stsize = st.size();
				}

				if (cur == p || cur == q)	// 其等于两节点任意一个均可
				{
					if (!tmp)
					{
						tmp = cur;
						stsize = st.size();
					}
					else
					{
						return tmp;
					}
				}
				st.pop();
				cur = cur->right;
			}
		}
		return nullptr;
    }
};
1.3.6 二叉树的前序遍历

借助栈操作,非递归写法:
在这里插入图片描述

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ret;
        stack<TreeNode *> st;
        auto cur = root;
        while (cur) {
            ret.push_back(cur -> val);
            if (cur -> right) {
                st.push(cur -> right);
            }
            if (cur -> left) {
                cur = cur -> left;
            }
            else {
                if (st.empty()) {	// 栈空时需要判断,否则取不出来top()
                    cur = nullptr;
                }
                else {
                    cur = st.top();
                    st.pop();
                }
            }
        }
        return ret;
    }
};
1.3.7 二叉树的中序遍历

借助栈操作,非递归写法:
在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        auto cur = root;
        stack<TreeNode *> st;
        vector<int> ret;
        while (cur || !st.empty()) {    // 均空跳出
            for (; cur; cur = cur -> left) {    // 按照链表遍历方式,左孩子全部入栈
                st.push(cur);
            }
            if (!st.empty()) {
                cur = st.top();
                st.pop();
                ret.push_back(cur -> val);  // 左孩子遍历结束,打印当前节点
                
                cur = cur -> right;     // 进入右孩子,若右孩子为空,则for循环不进入,继续取栈顶即可
            }
        }
        return ret;
    }
};
1.3.8 \color{red}{1.3.8 二叉树的后序遍历}

借助栈操作,非递归写法:
在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        auto cur = root;
        stack<TreeNode *> st;
        stack<bool> tag;     // 左子树访问标记,与栈st同步push同步pop
        vector<int> ret;     
        // 栈中二叉树的每一个节点到在tag标志位上都存在与其对应标志的tag标签,标记其是否被访问过
        while (cur || !st.empty()) {
            // 过程1
            for (; cur; cur = cur -> left) {    // 自己和左子树全部入栈,tag标志位同步进入并置为false
                st.push(cur);
                tag.push(false);
            }
            // 过程3
            while (!st.empty() && tag.top()) {
                cur = st.top();
                ret.push_back(cur -> val);
                st.pop();
                tag.pop();

                cur = nullptr;      // 栈为空时,cur手动置空
            }
            // 过程2
            // 由过程3进入过程2说明左子树访问完毕
            // 由过程1进入过程2说明左子树为空
            if (!st.empty()) {
                tag.top() = true;
                cur = st.top();     // top的左孩子访问完毕
                cur = cur -> right;     // 进入cur右孩子

            }
        }
        return ret;
    }
};
发布了209 篇原创文章 · 获赞 42 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/yl_puyu/article/details/103417551
今日推荐