一种二叉树非递归遍历的简单写法

一种二叉树非递归遍历的简单写法


二叉树的遍历是数据结构中非常基础的一个知识点,也是面试手撕代码环节的一个常见题目。这个问题的递归写法是比较简单的,但是如果面试官要求使用非递归写法,那么恭喜你同学,你被卷了。

这里提供一种简单的写法,我这里的简单,是指分析方法简单,而不是简洁(简洁的代码要看懂未必简单)。只要把循环中可能出现的各种情况分析清楚,那么写代码时只是把一堆if else填进去而已。

我们的分析方法的要点是:在每一轮循环中,把握住当前节点的状态,以及其左、右子树是否为空。下面我们将依次对先序、中序和后序遍历进行讨论。

先序遍历

在这里插入图片描述

针对当前节点,其左右子树无非下表列的四种情况。当右子树为空时,当前节点无需入栈,因为我们不需要再回头访问它的右子树了。当左子树为空但右子树非空时,当前节点也无需入栈,因为我们下一个就要访问它的右子树了。

同时我们需要一个标志变量,记录下当前的节点是否是从栈里弹出来的,如果是,那么不输出当前节点,并开始访问其右子树。

当前节点不是从栈里弹出来的当前节点是从栈里弹出来的
左非空,右非空输出当前节点,当前节点入栈,置左子树为当前节点不输出当前节点,当前节点不入栈,置右子树为当前节点
左非空,右空输出当前节点,当前节点不入栈,置左子树为当前节点不存在这种情况,因为没有入栈
左空,右非空输出当前节点,当前节点不入栈,置右子树为当前节点不存在这种情况,因为没有入栈
左空,右空输出当前节点,当前节点不入栈,从栈中弹出节点置为当前节点不存在这种情况,因为没有入栈

好了,有了上面这个表,我们只需要把这个逻辑填进循环体就可以了,不过是一堆if else

循环终止的条件是什么?栈中保存的是我们需要回头访问其右子树的节点。栈为空,说明整个二叉树中此时已经不存在还需要访问的右子树了,而按照先序遍历的顺序,右子树是最后才访问的,因此整个二叉树中已经不存在需要访问的节点了。所以栈为空时循环结束。

代码如下:


std::vector<int> PreOrderTraversal(TreeNode* root) {

    std::vector<int> ans;
    if(!root) return ans;

    std::stack<TreeNode*> stack_node;
    TreeNode*             cur_node     = root;
    bool                  cur_is_poped = false;
    while(true) {
        if(!cur_is_poped) {
            if(cur_node->left) {
                if(cur_node->right) stack_node.push(cur_node);
                ans.push_back(cur_node->val);
                cur_node = cur_node->left;
            } else if(cur_node->right) {
                ans.push_back(cur_node->val);
                cur_node = cur_node->right;
            } else {
                ans.push_back(cur_node->val);
                if(stack_node.empty()) break;
                cur_node = stack_node.top();
                stack_node.pop();
                cur_is_poped = true;
            }
        } else {
            cur_node     = cur_node->right;
            cur_is_poped = false;
        }
    }
    return ans;
}

中序遍历

在这里插入图片描述

中序遍历的情况相对简单。按照先序遍历的分析方法,我们可以得到类似的表格。

左非空,右非空输出当前节点,置右子树为当前节点,下一轮循环需对右子树进行遍历
左非空,右空输出当前节点,从栈中弹出节点置为当前节点
左空,右非空输出当前节点,置右子树为当前节点,下一轮循环需对右子树进行遍历
左空,右空输出当前节点,从栈中弹出节点置为当前节点

代码如下:

std::vector<int> InOrderTraversal(TreeNode* root) {
    std::vector<int> ans;
    if(!root) return ans;
    std::stack<TreeNode*> s;

    TreeNode* cur_node     = root;
    bool      cur_is_poped = false;
    while(true) {
        if(!cur_is_poped) {
            while(cur_node) {
                s.push(cur_node);
                cur_node = cur_node->left;
            }
        }

        if(s.empty()) break;
        cur_node = s.top();
        s.pop();
        ans.push_back(cur_node->val);
        cur_is_poped = true;
        if(cur_node->right) {
            cur_node     = cur_node->right;
            cur_is_poped = false;
        }
    }
    return ans;
}

可以看出,所有的节点都是先入栈,再出栈并输出的。因此循环中止的条件,仍然是栈为空。

后序遍历

在这里插入图片描述

后序遍历比中序遍历复杂的地方在于:中序遍历时,栈顶元素必然被出栈并输出;但是后序遍历需要先访问其右子树,然后再输出该节点。我们需要一个标记变量,标记栈中的元素是否已经在栈顶出现过了,如果没有出现过,且其右子树不为空,那我们需要先访问其右子树。如果出现过,那么直接输出。

当前节点未在栈顶出现过当前节点已在栈顶出现过
左非空,右非空不输出当前节点,当前节点不出栈,置右子树为当前节点输出当前节点,当前节点出栈,从栈中弹出节点置为当前节点
左非空,右空输出当前节点,当前节点出栈,从栈中弹出节点置为当前节点不存在,因为直接出栈了
左空,右非空不输出当前节点,当前节点不出栈,置右子树为当前节点输出当前节点,当前节点出栈,从栈中弹出节点置为当前节点
左空,右空输出当前节点,当前节点出栈,从栈中弹出节点置为当前节点不存在,因为直接出栈了
std::vector<int> PostOrderTraversal(TreeNode* root) {
    std::vector<int> ans;
    if(!root) return ans;

    using NodePair = std::pair<TreeNode*, bool>;
    std::stack<NodePair> s;

    NodePair cur_node_pair;

    TreeNode* cur_node = root;
    while(true) {
        while(cur_node) {
            s.push(std::make_pair(cur_node, false));
            cur_node = cur_node->left;
        }

        if(s.empty()) break;
        cur_node_pair = s.top();
        if(!cur_node_pair.second) {  //当前节点未在栈顶出现过
            if(cur_node_pair.first->right) { 
                cur_node             = cur_node_pair.first->right;
                cur_node_pair.second = true;
                s.top()              = cur_node_pair;
            } else {
                ans.push_back(cur_node_pair.first->val);
                s.pop();
            }
        } else { //当前节点已在栈顶出现过
            ans.push_back(cur_node_pair.first->val);
            s.pop();
        }
    }

    return ans;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值