二叉树的四种遍历

二叉树的四种遍历

二叉树

二叉树是一种数据结构,简单来说就是一个包含节点,以及它的左右孩子的一种数据结构。

二叉树

遍历方式

如果对每一个节点进行编号,你会以什么方式去遍历每个节点呢?

二叉树

如果你按照根节点->左孩子->右孩子的方式进行遍历,即先序遍历,每次先遍历根节点,遍历结果为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)放在不同位置即可,然后调用这个递归函数就可以了,代码完全一样。

  1. 前序遍历

    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
    
  2. 中序遍历

    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
    
  3. 后序遍历

    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
    

一样的代码,稍微调用一下位置就可以,如此固定的套路,使得只掌握递归解法并不足以令别人信服,因此我们有必要掌握迭代解法,同时也会加深我们对数据结构的理解。

迭代解法

  1. 前序遍历

    常规解法:

    使用栈来进行迭代,过程如下:

    • 初始化栈,并将根节点入栈;
    • 当栈不为空时:
    • 弹出栈顶元素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
  1. 中序遍历

    和前序遍历的代码完全相同,只是在出栈的时候才将节点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
    
  2. 后序遍历

    模板解法

    继续按照上面的思想,这次我们反着思考,节点先到达最右端的叶子节点并将路径上的节点入栈;

    然后每次从栈中弹出一个元素后,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
```
  1. 层次遍历

    二叉树的层次遍历的迭代方法与前面不同,因为前面的都采用了深度优先搜索的方式,而层次遍历使用了广度优先搜索,广度优先搜索主要使用了队列实现,也就不能使用前面的模板解法了。

    广度优先搜索的步骤为:

    • 初始化队列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的左或者右子树
返回结果

你可能感兴趣的:(二叉树的四种遍历)