algorithm-base/animation-simulation/二叉树/二叉树的后续遍历 (迭代).md
2021-07-29 02:33:38 +00:00

142 lines
5.0 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

之前给大家介绍了二叉树的[前序遍历]()[中序遍历]()的迭代法和 Morris 方法今天咱们来说一下二叉后序遍历的迭代法及 Morris 方法
阅读该文章前建议各位先阅读之前的三篇文章对该文章的理解有很大帮助
## 迭代
后序遍历的相比前两种方法难理解了一些所以这里我们需要认真思考一下每一行的代码的作用
我们先来复习一下二叉树的后序遍历
![](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/后序遍历.2bx6qccr1q1w.gif)
我们知道后序遍历的顺序是,` 对于树中的某节点, 先遍历该节点的左子树, 再遍历其右子树, 最后遍历该节点`
那么我们如何利用栈来解决呢
我们直接来看动画看动画之前但是我们`需要带着问题看动画`问题搞懂之后也就搞定了后序遍历
1.动画中的橙色指针发挥了什么作用
2.为什么动画中的某节点为什么出栈后又入栈呢?
好啦下面我们看动画吧
![后序遍历迭代](https://img-blog.csdnimg.cn/20210622160754912.gif)
相信大家看完动画之后也能够发现其中规律
我们来对其中之前提出的问题进行解答
1.动画中的橙色箭头的作用
> 用来定位住上一个访问节点这样我们就知道 cur 节点的 right 节点是否被访问如果被访问我们则需要遍历 cur 节点
2.为什么有的节点出栈后又入栈了呢
> 出栈又入栈的原因是我们发现 cur 节点的 right 不为 null 并且 cur.right 也没有被访问过因为 `cur.right != preNode `所以当前我们还不能够遍历该节点应该先遍历其右子树中的节点
>
> 所以我们将其入栈后然后`cur = cur.right`
```java
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
List<Integer> list = new ArrayList<>();
TreeNode cur = root;
//这个用来记录前一个访问的节点,也就是橙色箭头
TreeNode preNode = null;
while (cur != null || !stack.isEmpty()) {
//和之前写的中序一致
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
//1.出栈,可以想一下,这一步的原因。
cur = stack.pop();
//2.if 里的判断语句有什么含义?
if (cur.right == null || cur.right == preNode) {
list.add(cur.val);
//更新下 preNode也就是定位住上一个访问节点。
preNode = cur;
cur = null;
} else {
//3.再次压入栈,和上面那条 1 的关系?
stack.push(cur);
cur = cur.right;
}
}
return list;
}
}
```
Swift Code
```swift
class Solution {
func postorderTraversal(_ root: TreeNode?) -> [Int] {
var list:[Int] = []
var stack:[TreeNode] = []
var cur = root, preNode: TreeNode?
while !stack.isEmpty || cur != nil {
//和之前写的中序一致
while cur != nil {
stack.append(cur!)
cur = cur!.left
}
//1.出栈,可以想一下,这一步的原因。
cur = stack.popLast()
//2.if 里的判断语句有什么含义?
if cur!.right === nil || cur!.right === preNode {
list.append(cur!.val)
//更新下 preNode也就是定位住上一个访问节点。
preNode = cur
cur = nil
} else {
//3.再次压入栈,和上面那条 1 的关系?
stack.append(cur!)
cur = cur!.right
}
}
return list
}
}
```
Go Code:
```go
func postorderTraversal(root *TreeNode) []int {
res := []int{}
if root == nil {
return res
}
stk := []*TreeNode{}
cur := root
var pre *TreeNode
for len(stk) != 0 || cur != nil {
for cur != nil {
stk = append(stk, cur)
cur = cur.Left
}
// 这里符合本文最后的说法,使用先获取栈顶元素但是不弹出,根据栈顶元素的情况进行响应的处理。
temp := stk[len(stk) - 1]
if temp.Right == nil || temp.Right == pre {
stk = stk[: len(stk) - 1]
res = append(res, temp.Val)
pre = temp
} else {
cur = temp.Right
}
}
return res
}
```
当然也可以修改下代码逻辑将 `cur = stack.pop()` 改成 `cur = stack.peek()`下面再修改一两行代码也可以实现这里这样写是方便动画模拟大家可以随意发挥
时间复杂度 On, 空间复杂度 On
这里二叉树的三种迭代方式到这里就结束啦大家可以进行归纳总结三种遍历方式大同小异建议各位掌握之后自己手撕一下从搭建二叉树开始