二叉树
二叉树
数据结构
链表
数组
遍历
递归
迭代
前序遍历
前序遍历是中左右,根据栈FILO的特性,节点入栈的顺序就应该是右左中。因此,先将根节点入栈,当栈非空时,先弹出栈顶并访问该节点,该节点对应中;再将非空右节点和非空左节点入栈,下一轮处理的时候,左就变成了中进行处理,被弹出访问,再将右和左入栈。简言之,此处仅仅访问被定义为中的节点,再入栈右节点和左节点,其实这里的中是相对与自身而言的,但是对于父节点来说是左右节点。对于空节点不会被入栈。
这样思考有点陷入思考递归全过程中。。。其实就是先访问中节点,然后将右节点和左节点入栈。因为左节点自身马上就可以成为下一轮的中节点,自然符合了入栈顺序右左中。
1 |
|
上述处理在逻辑处理上更简单易懂,还有一种做法在逻辑上更加复杂,但为后续中序遍历开了先河。
首先不断访问节点并深入左子树直到为空,当节点为空则弹出栈顶,节点转向栈顶元素的右子树继续处理。此处的栈只是为了记录信息便于转向右子树。
1 |
|
避免非空节点的处理方式,只是用栈来记录非空节点,用来遍历整个树
具体过程:
栈非空时循环:只要栈中还有节点未被处理,继续遍历。
弹出栈顶节点:
使用 stack[len(stack)-1] 获取栈顶节点 node。
更新栈 stack = stack[:len(stack)-1],移除栈顶元素。
访问当前节点:
将当前节点的值 node.Val 添加到结果切片 vals 中。
压入右子树和左子树:
按照 右子树先压栈、左子树后压栈 的顺序操作。
由于栈是后进先出结构,保证了左子树先被访问(符合前序遍历顺序)
1 |
|
中序遍历
为了解释清楚,我说明一下 刚刚在迭代的过程中,其实我们有两个操作:
- 处理:将元素放进result数组中
- 访问:遍历节点
分析一下为什么刚刚写的前序遍历的代码,不能和中序遍历通用呢,因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。
那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。
左中右,决定了要先深入到最底下的节点然后从子树往上走。但是不用像左右中考虑节点是否访问过,因为访问左子树能刚好把需要访问的左和中解决了,它能马上获得要访问的右子树。
节点非空则入栈并不断深入左子树,但不访问。节点为空,弹出栈顶并访问,转向处理右子树
1 |
|
二叉树的后序遍历
后序遍历的顺序是:
- 左子树 ->
- 右子树 ->
- 根节点
这意味着我们会在访问一个节点之前,先处理完它的左子树和右子树。相比前序遍历和中序遍历,后序遍历的实现更复杂,尤其在非递归实现中需要额外判断,以避免错误地重复访问节点。
代码回顾
左右中,左决定了要先深入到最底下的节点然后从子树往上走。访问完左子树后不能马上访问根节点,它要转向右子树进行处理,处理完右子树才能访问根节点,因此需要记录上次访问过的节点,根据记录得知右子树访问完了才能访问根节点。
不断深入左子树但是不访问,节点为空则获取栈顶元素,在访问当前节点之前要做条件判断(当前节点的右子树是否为空或者右子树访问过了),若满足则弹出栈顶元素并访问,标记该节点已经访问过,并将当前节点置为空表明要回溯到父节点,否则转向处理右子树。
1 |
|
算法思路
用栈模拟递归调用:
- 在递归实现中,我们先递归处理左子树,然后右子树,最后访问根节点。在非递归实现中,我们使用栈来模拟递归的行为。
判断右子树是否已经访问:
- 当弹出一个节点时,我们要判断它的右子树是否已经访问过。如果右子树未访问,我们转向右子树;如果右子树为空或者已访问过,我们才访问当前节点。
使用
prev
变量:- 记录上一个访问过的节点,用于判断是否需要访问当前节点,避免重复访问。
如何处理每种情况
左子树的遍历:
- 我们不断将当前节点的左子树入栈,这样可以保证左子树优先处理。
右子树的判断:
- 当访问到一个节点时,首先检查右子树是否为空或已经访问过。
- 如果右子树为空或访问过:我们弹出栈顶节点并访问它。
- 如果右子树未访问:我们转向右子树继续遍历。
- 当访问到一个节点时,首先检查右子树是否为空或已经访问过。
回溯:
- 当左右子树都处理完后,我们将当前节点设置为空,表明需要回溯到父节点。
示例:
二叉树结构:
1 |
|
- 后序遍历结果:
[4, 5, 2, 3, 1]
执行过程
初始状态:
stack = []
,node = 1
- 将 1 入栈,继续向左遍历,
stack = [1]
,node = 2
- 将 1 入栈,继续向左遍历,
将 2 入栈,继续向左遍历,
stack = [1, 2]
,node = 4
- 4 没有左子树,将 4 入栈后设置
node = nil
- 4 没有左子树,将 4 入栈后设置
弹出 4,访问它(因为它没有右子树),
vals = [4]
- 设置
prev = 4
,回溯到 2,继续处理右子树
- 设置
弹出 5,访问它,
vals = [4, 5]
,回溯到2