前言
在前面我们学习了二叉树的递归遍历,但是,众所周知,递归是比较消耗资源的,所以,在有些注重性能的地方,会需要使用到非递归遍历,在这里介绍一下,二叉树的非递归遍历。
本质上:二叉树的非递归遍历也是模拟递归遍历,递归遍历中需要产生深层次的函数栈帧,而非递归就是将这些深层次的函数栈帧存起来模拟递归。
前序遍历
前序遍历的遍历顺序是:根 + 左子树 + 右子树
我们将其转化为非递归,就在于
- 先访问左路节点
- 在访问左路节点的右子树
也就是我们采用栈的数据结构,
在每一次访问左路节点的时候将其入栈
左路节点访问完之后,开始取栈顶元素,去访问其右子树,
-----此图借鉴于csdn博主"想写好代码的小猫头"
按照先序遍历的思路,栈里面的过程如下:
C++
void prevOrder(Node* root)
{
stack<Node*> s;
Node* cur = root;
while (cur || !s.empty())
{
while (cur)
{
cout << cur->_data << " ";
s.push(cur);
cur = cur->_left;
}
Node* top = s.top();
s.pop();
cur = top->_right;
}
}
中序遍历
中序遍历的顺序是:左子树 + 根 + 右子树
我们将其转化为非递归,其实和前序遍历非常像,只是在于,访问根节点的时间不同了
前序遍历是每次遇到新节点的时候就直接访问新节点,
而中序遍历则是在访问完左路节点之后,开始访问根节点(也就是左路节点全部入栈之后才访问根节点)
直接写代码吧
C++
void inOrder(Node* root)
{
stack<Node*> s;
Node* cur = root;
while (cur || !s.empty())
{
while (cur)
{
s.push(cur);
cur = cur->_left;
}
Node* top = s.top();
s.pop();
cout << top->_data << " ";
cur = top->_right;
}
}
后序遍历
后序遍历相较于前两种遍历麻烦一点,但也很简单
后序遍历的难点在于,当访问到一个节点的时候,我们需要知道这个节点的右子树有没有访问,
如果没有访问,就要先访问右子树,否则才能访问自己。
解决这个问题有两个办法
- 使用一个flag来标记,右子树有没有被访问,但是控制起来有点麻烦
- 下面详细说一下这种办法,比较巧妙
方法二:
当我们访问到一个节点的时候,我们可以记录下前一个访问的节点
如果前一个访问的节点是该节点的右子节点,说明该节点的右子树访问完了(后序遍历,左子树 右子树 根)
如果前一个访问的节点不是该节点的右子节点,说明该节点的右子树没有访问完,需要访问右子树
代码
C++
void postOrder(Node* root)
{
stack<Node*> s;
Node* cur = root;
Node* prev = nullptr;
while (cur || !s.empty())
{
while (cur)
{
s.push(cur);
cur = cur->_left;
}
Node* top = s.top();
if (prev == top->_right || top->_right == nullptr)
{
cout << top->_data << "";
prev = top;
s.pop();
}
else
{
cur = top->_right;
}
}
}