所有方法都可以在力扣上验证
前序题目链接
中序题目链接
后序题目链接
结构定义
// Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
思路
递归转换为非递归需要用到栈,而前中后三种遍历方式都有一种统一的思路,只不过后序比较特殊一点
这里先给出大体的框架。
vt是遍历的结果,st存放的是遍历过程中的结点,对root进行操作,根据需要让它转到左孩子或者右孩子,并且在遇到空结点的时候做出相应操作
vector<int> func(TreeNode* root){
vector<int> vt;
if(!root)return vt;
stack<TreeNode*> st;
while(root||!st.empty()){
if(root){
根据需要操作
}
else{
根据需要操作
}
}
}
前序迭代
方法一 通用方法
vector<int> preorderTraversal(TreeNode* root) {
vector<int>vt;
if(!root)return vt;
stack<TreeNode*>st;
while(root||!st.empty()){
if(root){
//访问根结点
vt.push_back(root->val);
//根节点入栈,在root为空时退回到该子树的根结点
st.push(root);
//遍历左子树
root=root->left;
}
else{
//取出该子树的根节点
root=st.top();
st.pop();
//访问右子树
root=root->right;
}
}
return vt;
}
可以模拟一下加深印象
执行结果
方法二
先序遍历还有一个方法,结构很像递归遍历
vector<int> preorderTraversal(TreeNode* root) {
vector<int> vt;
if(!root) return vt;
stack<TreeNode*> st;
st.push(root);
while(!st.empty())
{
//取栈顶,访问根节点
TreeNode* node=st.top();
st.pop();
vt.push_back(node->val);
//右孩子先入栈,这样能保证左孩子先被访问
if(node->right) st.push(node->right);
if(node->left) st.push(node->left);
}
return vt;
}
运行结果
中序迭代
方法一 通用方法
这里在遇到空结点的时候才访问栈顶元素,建议多模拟几遍
vector<int> inorderTraversal(TreeNode* root) {
vector<int>vt;
stack<TreeNode*>st;
while(root||!st.empty()){
if(root){
//先入栈,后面访问
st.push(root);
//访问左子树
root=root->left;
}
else{ //此时root走到了空结点
//访问栈顶元素,也就是当前左子树的根节点
root=st.top();
st.pop();
vt.push_back(root->val);
//访问右子树,执行同样的操作
root=root->right;
}
}
return vt;
}
后序迭代
方法一 通用方法
后序迭代的最关键的地方就是根节点的访问时机,由于后序遍历是按照 左-右-中 的顺序,所以迭代过程中我们应该先处理左子树,返回到根节点,再处理右子树,再访问根节点。迭代过程会遇到两次根节点,而第二次才能访问根节点。这里处理方法有很多,比如借助标记结点,借用辅助栈,更改树的结构等等。不过我觉得最简洁的方法还是借助标记结点。
先写伪代码
vector<int> inorderTraversal(TreeNode* root) {
while(root存在 或者 栈非空){
if(root){
入栈当前结点
root往左走
}
else{
root=栈顶
if(root的右孩子不存在 或者 标记节点就是当前结点的右孩子){
出栈
访问root
标记节点置为当前结点
root=NULL,因为当前根节点的左右子树和当前结点已经访问完了,应该退回到上一层,继续取栈顶
}
else{
root往右走
}
}
}
}
下面是代码
vector<int> postorderTraversal(TreeNode* root) {
vector<int> vt;
if(!root) return vt;
stack<TreeNode*> st;
TreeNode* last_visited_node=NULL;
while(root||!st.empty()){
if(root){
st.push(root);
root=root->left;
}
else{
root=st.top();
if(!root->right||last_visited_node==root->right){
st.pop();
vt.push_back(root->val);
last_visited_node=root;
root=NULL;
}
else{
root=root->right;
}
}
}
return vt;
}
运行结果
方法二
这里方法二其实也很像递归,先入左孩子,再入右孩子,最后翻转vt就是正确的遍历结果了
vector<int> postorderTraversal(TreeNode* root) {
vector<int> vt;
stack<TreeNode*> st;
st.push(root);
TreeNode* t=NULL;
while(!st.empty()){
t=st.top();
st.pop();
vt.push_back(t->val);
if(t->left) st.push(t->left);
if(t->right) st.push(t->right);
}
reverse(vt.begin(),vt.end());
return vt;
}
颜色标记法
力扣上一个精选评论写的方法
其核心思想如下:
- 使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。
- 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。
- 如果遇到的节点为灰色,则将节点的值输出。
下面是中序的代码,至于先序和后序只需要调整入栈顺序即可
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
WHITE, GRAY = 0, 1
res = []
stack = [(WHITE, root)]
while stack:
color, node = stack.pop()
if node is None: continue
if color == WHITE:
stack.append((WHITE, node.right))
stack.append((GRAY, node))
stack.append((WHITE, node.left))
else:
res.append(node.val)
return res
作者:hzhu212
链接:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/yan-se-biao-ji-fa-yi-chong-tong-yong-qie-jian-ming/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
下面是评论区里的c++代码
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int>ans;
int white = 0;
int gray = 1;
stack<pair<int, TreeNode*>>s;
s.push(make_pair(white,root));
while (!s.empty())
{
int color = s.top().first;
TreeNode* t = s.top().second;
s.pop();
if (t == NULL) continue;
if (color == white)
{
if(t->right)s.push(make_pair(white, t->right));
s.push(make_pair(gray, t));
if(t->left)s.push(make_pair(white, t->left));
}
else ans.push_back(t->val);
}
return ans;
}
};
模拟了一下,确实是很简洁的方法,只不过结点会入栈两次,消耗大了点