二叉树的四种遍历
二叉树
二叉树是一种数据结构,简单来说就是一个包含节点,以及它的左右孩子的一种数据结构。
遍历方式
如果对每一个节点进行编号,你会以什么方式去遍历每个节点呢?
如果你按照根节点->左孩子->右孩子的方式进行遍历,即先序遍历,每次先遍历根节点,遍历结果为1,2,3,4,5,6,7
同理如果你按照左孩子->根节点->右孩子的方式遍历,即中序遍历,遍历结果为2,5,1,6,3,7
如果你按照左孩子->右孩子->根节点的方式遍历,即后序遍历,遍历结果为4,5,2,6,7,3,1
最后,层次遍历就是按照每一层从左享有的方式进行遍历,遍历结果就是1,2,3,4,5,6,7
题目解析
给定一个二叉树,让我们使用一个数组来返回遍历结果。
递归解法
由于层次遍历的递归解法不是主流,因此只介绍前三种递归解法。他们的模板相对比较固定,一般都会新增一个dfs函数
def dfs(root):
if not root:
return
res.append(root.val)# 前序遍历
dfs(root.left)
dfs(root.right)
对于前序、中序和后序遍历,只需要将递归函数里面的res.append(root.val)放在不同位置即可,然后调用这个递归函数就可以了,代码完全一样。
-
前序遍历
class solution: def preorderTravelsal(self,root:TreeNode)->List[int]: res = [] def dfs(root): nonlocal res# 为了让上一级定义的res可以在这个函数中用 if not root: return res.append(root.val) dfs(root.left) dfs(root.right) dfs(root) return res
-
中序遍历
class solution: def preorderTravelsal(self,root:TreeNode)->List[int]: res = [] def dfs(root): nonlocal res if not root: return dfs(root.left) res.append(root.val) dfs(root.right) dfs(root) return res
-
后序遍历
class solution: def preorderTravelsal(self,root:TreeNode)->List[int]: res = [] def dfs(root): nonlocal res if not root: return dfs(root.left) dfs(root.right) res.append(root.val) dfs(root) return res
一样的代码,稍微调用一下位置就可以,如此固定的套路,使得只掌握递归解法并不足以令别人信服,因此我们有必要掌握迭代解法,同时也会加深我们对数据结构的理解。
迭代解法
-
前序遍历
常规解法:
使用栈来进行迭代,过程如下:
- 初始化栈,并将根节点入栈;
- 当栈不为空时:
- 弹出栈顶元素node,并将值添加到结果中;
- 如果node的右子树非空,将右子树入栈;
- 如果node的左子树非空,将左子树入栈;
由于栈是先进先出的顺序,所以入栈时先将右子树入栈,这样使得前序遍历结果为"根->左->右"的顺序。
class solution: def preorderTravel(self,root:TreeNode)->List[int]: if not root :return [] stack,res=[root],[] while stack: node = stack.pop() if node: res.append(node.val) if node.right: stack.append(node.right) if node.left: stack.append(node.left) return res
模板解法:
你也可以直接启动“僵尸”模式,套用迭代的模板来一波“真香操作”。
模板解法的思路稍有不同,他先将根节点cur和所有的左孩子入栈并加入结果中,直至cur为空,用一个循环实现:
然后每弹出一个栈顶元素tmp,就到达它的右孩子,再将这个节点当做cur重新按照上面的步骤来一遍,直至栈为空。这里又需要一个while循环。
class solution:
def preorderTraversal(self,root:TreeNode)->List[int]:
if not root:return []
cur,stack,res = root,[],[]
while cur or stack:
while cur:# 根节点和左孩子入栈
res.append(cur.val)
stack.append(cur)
cur = cur.left
tmp = stack.pop()# 每弹出一个元素,就到达右孩子
cur = tmp.right
return res
-
中序遍历
和前序遍历的代码完全相同,只是在出栈的时候才将节点tmp的值加入到结果中。
模板解法
class solution: def inorderTraversal(self,root:TreeNode)->List[int]: if not root:return[] cur,stack,res = root,[],[] while cur or stack: while cur:# 入栈并到达最左端的叶子节点 stack.append(cur) cur = cur.left tmp = stack.pop() res.append(tmp.val)# 出栈时再加入结果 cur = tmp.right return res
-
后序遍历
模板解法
继续按照上面的思想,这次我们反着思考,节点先到达最右端的叶子节点并将路径上的节点入栈;
然后每次从栈中弹出一个元素后,cur到达它的左孩子,并将左孩子看做cur继续执行上面的步骤。
最后将结果反向输出即可。参考代码如下:
class solution: def postorderTraversal(self,root:TreeNode)->List[int]: if not root:return [] cur,stack,res = root,[],[] while cur or stakck: while cur: res.append(cur.val) stack.append(cur) cur = cur.right tmp = stack.pop() cur = tmp.left return res[::-1]
然而,后序遍历采用模板解法并没有按照真实的栈操作,而是利用了结果的特点反向输出,不免显得技术含量不足。
因此掌握标准的栈操作解法是必要的。
常规解法:
类比前序遍历的常规解法,我们只需要将输出的“根->左->右”的顺序改为“左->右->根”就可以了。
如何实现呢?这里有一个小技巧,我们入栈时额外加入一个标识,比如这里使用flag=0;
每次从栈中弹出元素时,如果flag为0,则需要将flag变为1并连同该节点再次入栈,只有当flag为1时才可以将该节点加入到结果中。
```python
class solution:
def postorderTraversal(self,root:TreeNode)->List[int]:
if not root :return[]
stack,res = [(0,root)],[]
while stack:
flag,node = stack.pop()
if not node:continue
if flag ==1:# 遍历过了的话,就加入到结果
res.append(node.val)
else:
stack.append((1,node))
stack.append((0,node.right))# 右
stack.append((0,node.left))# 左
return res
```
-
层次遍历
二叉树的层次遍历的迭代方法与前面不同,因为前面的都采用了深度优先搜索的方式,而层次遍历使用了广度优先搜索,广度优先搜索主要使用了队列实现,也就不能使用前面的模板解法了。
广度优先搜索的步骤为:
- 初始化队列q,并将根节点root加入到队列中;
- 当队列不为空时:
- 队列中弹出节点node,加入到结果中
- 如果左子树非空,左子树加入队列;
- 如果右子树非空,右子树加入队列;
由于题目要求每一层保存在一个子数组中,所以我们额外加入了level保存每层的遍历结果,并使用for循环来实现。
class solution:
def levelorder(self,root:TreeNode)->List[List[int]]:
if not root: return []
res,q = [],[root]
while q:
n = len(q)
level = []
for i in range(n):
node = q.pop(0) # 这里的q相当于一个队列
level.append(node.val)
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
res.append(level)
return res
总结
总结一下,在二叉树的前序、中序、后序遍历中,递归实现的伪代码为:
边界条件
定义 dfs 函数:
如果root为空,返回;
递归左子树 # 顺序可变
递归右子树
root的值加入到结果
执行递归函数,返回结果
迭代实现的伪代码为:
边界条件
初始化 cur,stack,root
while stack or cur非空:
while 循环:
cur 向左下或者右下遍历
cur 的值入栈
弹出节点tmp
cur回到tmp的左或者右子树
返回结果