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

 2023-09-07 阅读 18 评论 0

摘要:一种二叉树非递归遍历的简单写法 目录一种二叉树非递归遍历的简单写法先序遍历中序遍历后序遍历 二叉树的遍历是数据结构中非常基础的一个知识点,也是面试手撕代码环节的一个常见题目。这个问题的递归写法是比较简单的,但是如果面试官要求使用非递归写法,

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

目录

  • 一种二叉树非递归遍历的简单写法
    • 先序遍历
    • 中序遍历
    • 后序遍历

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

这里提供一种简单的写法,我这里的简单,是指分析方法简单,而不是简洁(简洁的代码要看懂未必简单)。只要把循环中可能出现的各种情况分析清楚,那么写代码时只是把一堆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;
}

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/12547.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息