二叉树的前中后序遍历(非递归实现)
之前我们已经提到过了二叉树的前中后序三种遍历方式,今天我们将采用非递归的方式来实现。
1. 前序遍历
我们需要借助 栈 这种数据结构来达到 递归 的目的
1.1 分析
- 首先我们将 根节点 入栈
- 循环取栈顶元素同时出栈,并将栈顶元素添加到结果集合中
- 判断当前节点的右孩子是否为空,非空入栈
- 判断当前节点的左孩子是否为空,非空入栈
1.2 举例说明
下面我们通过一个具体的例子来说明,如下图所示
首先我们将根节点A入栈,然后进入循环
- 首先打印A
- 接着判断其右孩子( C)是否为空,发现其不为空,将其入栈
- 接着判断其左孩子(B)是否为空,发现其不为空,将其入栈
第二次进入循环
- 取栈顶元素B,打印B
- 接着判断其右孩子(E)是否为空,发现其不为空,将其入栈
- 接着判断其左孩子(D)是否为空,发现其不为空,将其入栈
第三次进入循环
- 取栈顶元素D,打印D
- 接着判断其右孩子(null)是否为空,发现为空
- 接着判断其左孩子(null)是否为空,发现为空
第四次进入循环
- 取栈顶元素E,打印E
- 接着判断其右孩子(null)是否为空,发现为空
- 接着判断其左孩子(null)是否为空,发现为空
第五次进入循环
- 取栈顶元素C,打印C
- 接着判断其右孩子(null)是否为空,发现为空
- 接着判断其左孩子(null)是否为空,发现为空
循环结束,依次打印结果为:A B D E C
3. 代码实现
public List<Integer> preorderTraversal4(TreeNode root)
{
ArrayList<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root == null)
{
return list;
}
//首先将根节点入栈
stack.push(root);
//循环判断,当栈不为空
while (!stack.isEmpty())
{
//弹出栈顶元素
TreeNode cur = stack.pop();
//将其值添加到list集合
list.add(cur.val);
//如果当前节点的右孩子不为空,将其压栈
if (cur.right != null)
{
stack.push(cur.right);
}
//如果当前节点的左孩子不为空,将其压栈
if (cur.left != null)
{
stack.push(cur.left);
}
}
return list;
}
2. 中序遍历
2.1 分析
- 借助循环从根节点出发,依次向左子树查找,遇到的所有元素全部入栈,直至为空,退出循环
- 到达最左侧元素后,取栈顶元素同时出栈,将其添加到结果集合
- 把刚得到的栈顶元素的右子树作为起点,再次循环向左找,路径上的元素全部入栈
2.2 举例说明
下面我们通过一个具体的例子来说明,如下图所示
-
首先,我们借助循环一直向左子树查找直至空,依次将A B D入栈**(此时栈:A B D)**
-
接着取栈顶元素D,打印D (此时栈:A B)
-
把栈顶元素 D 的右子树null 作为起点,发现为空
-
接着取栈顶元素B,打印B (此时栈:A )
-
把栈顶元素 B 的右子树E作为起点,将其入栈**(此时栈:A E)**
-
接着取栈顶元素E,打印E (此时栈:A )
-
把栈顶元素 E 的右子树null 作为起点,发现为空
-
接着取栈顶元素A,打印A**(此时栈空 )**
-
把栈顶元素 A 的右子树C作为起点,将其入栈 (此时栈:C )
-
接着取栈顶元素C,打印C (此时栈空 )
栈空结束
2.3 代码
public List<Integer> inorderTraversal2(TreeNode root)
{
ArrayList<Integer> list = new ArrayList<>();
if (root == null)
{
return list;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (true)
{
//循环遍历向左找,直到为空
while (cur != null)
{
stack.push(cur);
cur = cur.left;
}
//2.如果为空,遍历结束
if (stack.isEmpty())
{
break;
}
//3.取栈顶元素并访问
TreeNode pop = stack.pop();
list.add(pop.val);
//4.从当前节点右子树出发
cur = pop.right;
}
return list;
}
3. 后序遍历
3.1 分析
- 借助循环从根节点出发,依次向左子树查找,遇到的所有元素全部入栈,直至为空,退出循环
- 判断栈顶元素(peek不是pop)是否能访问(一下两种情况可以访问)
- 栈顶元素没有右子树,将其pop
- 栈顶元素的右子树已经被访问过一次,将其pop
- 把刚得到的栈顶元素的右子树作为起点,再次循环向左找,路径上的元素全部入栈
3.2 举例说明
下面我们通过一个具体的例子来说明,如下图所示
- 首先,我们借助循环一直向左子树查找直至空,依次将A B D入栈(此时栈:A B D)
- 接着判断栈顶元素D,发现其没有右子树,将其出栈并打印
- 把栈顶元素 D 的右子树null 作为起点,发现为空
- 接着判断栈顶元素B,发现其存在右子树,但是还没访问过,将其右子树作为起点开始循环向左入栈(此时栈:A B E)
- 接着判断栈顶元素E,发现其没有右子树,将其出栈并打印
- 接着判断栈顶元素B**(第二次访问)**,发现其存在右子树,但是已经访问过,将其出栈并打印(此时栈:A )
- 接着判断栈顶元素A,发现其存在右子树,但是还没访问过,将其右子树作为起点开始循环向左入栈(此时栈:A C)
- 接着判断栈顶元素C,发现其没有右子树,将其出栈并打印(此时栈:A )
- 接着判断栈顶元素A**(第二次访问)**,发现其存在右子树,但是已经访问过,将其出栈并打印(此时栈空 )
- 结束
3.3 代码
public List<Integer> postorderTraversal(TreeNode root)
{
ArrayList<Integer> list = new ArrayList<>();
if (root == null)
{
return list;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode pre = null;
while (true)
{
//1.依次向左查找入栈,直至空
while (cur != null)
{
stack.push(cur);
cur = cur.left;
}
if (stack.isEmpty())
{
break;
}
//拿出栈顶元素,检查能否访问
TreeNode top = stack.peek();
//需要判断的两个条件
if (top.right == null || pre == top.right)
{
TreeNode pop = stack.pop();
list.add(pop.val);
//记录遍历完元素后的最后一个
pre = pop;
}
//不满足条件的话把刚得到的栈顶元素的右子树作为起点,再次循环向左找,路径上的元素全部入栈
else
{
cur = top.right;
}
}
return list;
}