前面介绍了深度优先遍历的 , 前中后序 三种遍历方式的迭代实现, 但是却没有向 递归那样的 统一实现;
原因 在于, 使用迭代方式的遍历过程中, 有两点注意
- 访问节点( 将节点压入栈中);
- 处理节点(将节点从栈中取出, 然后将节点中的数据部分存入到结果集中);
在前序遍历中, 访问节点与 处理节点这两个步骤 可以同步进行, 而中序遍历则不行;
难道 二叉树前后中序遍历的迭代法实现,就不能风格统一么(即前序遍历 改变代码顺序就可以实现中序 和 后序)
迭代法的 统一实现思想
造成上述, 访问节点与 处理节点不能同步的原因时:
- 访问节点时, 是将节点压入栈中;
- 处理节点时, 是将节点的数值 存入到 结果集中;
这两个步骤, 是将节点 存入到 不同的对象中;
那么 迭代法 统一实现的思想便是:
- 将访问节点 与 处理节点 都放入栈中;
- 但是处理节点 放入栈中之后,需要做标记;
leetcode: 144 前序遍历
94
145
1. 前序遍历的迭代;
1.1 统一的步骤
统一的方式:
根节点入栈:
循环开始:取出栈顶节点的引用赋值为当前节点;
条件判断, 若当前节点不为空, 开始访问节点(节点入栈):
- 弹出栈顶节点,
- 条件判断,当前节点的右子树不空, 将当前节点右子树 入栈;
- 条件判断, 当前节点的左子树不空, 将当前节点的左子树入栈;
- 当前节点入栈,
- 标记节点入栈,(使用空节点作为标记节点)
否则,当前节点为空节点, 开始处理节点(节点中数值,存入结果集中)
- 弹出栈顶节点;(空节点,为标记节点, 代表移除空节点后, 此时的栈顶节点才是 需要处理的 中间节点)
- 当前节点 赋值为 栈顶节点的引用;
- 弹出栈顶节点;
- 将当前节点中的数据值 存入到结果集中;
返回结果集;
1.2 code
class Solution1{
public:
vector<int> preorder(TreeNode* root){
vector<int> result;
stack<TreeNode*> st;
if(root == nullptr) return result ;
st.push(root);
while(!st.empty()){
TreeNode* cur_node = st.top();
if(cur_node != nullptr){// 如果当前节点,不为空,开始访问节点, 即节点入栈;
st.pop();
if(cur_node->right) st.push(cur_node->right); // 当前节点的右子树存在, 右子树入栈;
if(cur_node->left) st.push(cur_node->left); // 左子树存在, 左子树与入栈;
st.push(cur_node); // 当前节点入栈;
st.push(nullptr); // 标记节点入栈;
}else{// 当前节点为空, 表示这个节点为标记节点, 移除该标记节点后, 可以处理节点了, 将节点中的数值保存到结果集中;
st.pop();
cur_node = st.top();
result.push_back(cur_node->val);
st.pop();
}
}
return result;
}
};
2. 中序遍历的迭代;
2.1 步骤
统一的方式:
根节点入栈:
循环开始:取出栈顶节点的引用赋值为当前节点;
条件判断, 若当前节点不为空, 开始访问节点(节点入栈):
- 弹出栈顶节点,
- 条件判断,当前节点的右子树不空, 将当前节点右子树 入栈;
- 当前节点入栈,
- 标记节点入栈, (使用空节点作为标记节点)
- 条件判断, 当前节点的左子树不空, 将当前节点的左子树入栈;
否则,当前节点为空节点, 开始处理节点(节点中数值,存入结果集中)
- 弹出栈顶节点;( 空节点,为标记节点, 代表移除空节点后, 此时的栈顶节点才是 需要处理的 中间节点)
- 当前节点 赋值为 栈顶节点的引用;
- 弹出栈顶节点;
- 将当前节点中的数据值 存入到结果集中;
返回结果集;
2.2 code
#include "vector"
#include "stack"
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x): val(x), left(nullptr), right(nullptr){}
};
class Solution2 {// inorder;
public:
vector<int> inorderTraversal(TreeNode *root) {
vector<int> result;
stack<TreeNode *> st;
if (root != nullptr) st.push(root);
while (!st.empty()) {
TreeNode *cur_node = st.top();
if (cur_node != nullptr) {
st.pop(); // 弹出节点,
if (cur_node->right) st.push(cur_node->right);// 当前节点的右子树入栈,开始访问当前节点的右子树;;
st.push(cur_node); // 当前节点入栈;
st.push(nullptr); // 标记节点入栈;
if (cur_node->left) st.push(cur_node->left);
} else { // 否则, 当前节点为空节点, 开始处理节点,
st.pop();// 弹出空节点,这是标记节点, 代表栈顶节点移除空节点后, 此时的栈顶节点是需要处理,存入结果集中的;
cur_node = st.top();
st.pop();
result.push_back(cur_node->val);
}
}
return result;
}
};
3. 中序遍历的迭代;
3.1 步骤
统一的方式:
根节点入栈:
循环开始:取出栈顶节点的引用赋值为当前节点;
条件判断, 若当前节点不为空, 开始访问节点(节点入栈):
- 弹出栈顶节点,
- 当前节点入栈,
- 标记节点入栈, (使用空节点作为标记节点)
- 条件判断,当前节点的右子树不空, 将当前节点右子树 入栈;
- 条件判断, 当前节点的左子树不空, 将当前节点的左子树入栈;
否则,当前节点为空节点, 开始处理节点(节点中数值,存入结果集中)
- 弹出栈顶节点;( 空节点,为标记节点, 代表移除空节点后, 此时的栈顶节点才是 需要处理的 中间节点)
- 当前节点 赋值为 栈顶节点的引用;
- 弹出栈顶节点;
- 将当前节点中的数据值 存入到结果集中;
返回结果集;
3.2 code
#include "vector"
#include "stack"
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x): val(x), left(nullptr), right(nullptr){}
};
class Solution3{
public:
vector<int> preorder(TreeNode* root){
vector<int> result;
stack<TreeNode*> st;
if(root == nullptr) return result ;
st.push(root);
while(!st.empty()){
TreeNode* cur_node = st.top();
if(cur_node != nullptr){// 如果当前节点,不为空,开始访问节点, 即节点入栈;
st.pop();
st.push(cur_node); // 当前节点入栈;
st.push(nullptr); // 标记节点入栈;
if(cur_node->right) st.push(cur_node->right); // 当前节点的右子树存在, 右子树入栈;
if(cur_node->left) st.push(cur_node->left); // 左子树存在, 左子树与入栈;
}else{// 当前节点为空, 表示这个节点为标记节点, 移除该标记节点后, 可以处理节点了, 将节点中的数值保存到结果集中;
st.pop();
cur_node = st.top();
result.push_back(cur_node->val);
st.pop();
}
}
return result;
}
};