算法随笔-二叉树遍历的N种姿势

最近在练习用Python刷算法,leetcode上刷了快300题。一开始怀疑自己根本不会写代码,现在觉得会写一点点了,痛苦又充实的刷题历程。对我这种半路出家的人而言,收获真的很大。

今天就从二叉树遍历写起,曾经有次面试就被迭代实现卡过。。。

 

 

最简单的递归


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#先序遍历
def preorderTraversal(self, root: TreeNode) -> List[int]:
        res=[]
        def preTraversal(node,result):
            if node==None:
                return
            #输出放在最前
            result.append(node.val)
            preTraversal(node.left,result)
            preTraversal(node.right,result)
        preTraversal(root,res)
        return res
 
#中序遍历
def inorderTraversal(self, root: TreeNode) -> List[int]:
        res=[]
        def inorderTraversal(node,result):
            if node==None:
                return
            inorderTraversal(node.left,result)
            #输出放在中间
            result.append(node.val)
            inorderTraversal(node.right,result)
        inorderTraversal(root,res)
        return res
 
#后序遍历
def postorderTraversal(self, root: TreeNode) -> List[int]:
        res=[]
        def postTraversal(node,result):
            if node==None:
                return
            postTraversal(node.left,result)
            postTraversal(node.right,result)
            #输出放在最后
            result.append(node.val)
        postTraversal(root,res)
        return res

  递归实现极其简单,但是面试时往往会更进一步,问几个深入一点的问题:

  1. 递归实现存在什么问题? 
  2. 尾递归是什么?

这两个问题都在讲一件事,就是迭代实现的二叉树遍历会存在StackOverflow异常。却决于操作系统和运行时,不同的程序拥有的栈大小不一,但是栈的容量都是较小的,基于递归的实现,如果未优化,就会导致堆栈溢出的异常。对.NET而言,系统分配的默认栈空间大小是1MB,树节点一多,很容就满了。

而尾递归则是对普通递归的优化,每次迭代最后都是直接调用自身。很多编译器都对尾递归做了生成优化,使得它可以不在调用栈上面每次都添加一个新的堆栈帧,而是更新它。这样就不会导致调用栈爆炸的异常。

如果你能答上上面的问题,往往会让你写一下二叉树遍历非递归的实现,这里难度就上了一个台阶。

 

非递归实现


 

二叉树迭代一定会用到栈!二叉树迭代一定会用到栈!二叉树迭代一定会用到栈!

talk is easy, show you my code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#超简单的先序遍历
def preorderTraversal(self, root: TreeNode) -> List[int]:
        res=[]
        if root==None:
            return
        stack=[root]
        while stack:
            node=stack.pop()
            res.append(node.val)
            #栈先进后出,顺序要注意
            if node.right:
                stack.append(node.right)
            if node.left:
                stack.append(node.left)
        return res
 
#稍复杂的中序遍历
def inorderTraversal(self, root: TreeNode) -> List[int]:
        res=[]
        curr=root
        stack=[]
        while curr or stack:
            #优先遍历全部左节点
            while curr:
                stack.append(curr)
                curr=curr.left
            node=stack.pop()
            res.append(node.val)
            #当前节点切换到右节点
            if node.right:
                curr=node.right
        return res
 
#最复杂的后序遍历
#解法1 基于先序遍历的变形  leetcode官方题解:https://leetcode-cn.com/problems/binary-tree-postorder-traversal/solution/er-cha-shu-de-hou-xu-bian-li-by-leetcode/
 
def postorderTraversal(self, root: TreeNode) -> List[int]:
        res=[]
        if root==None:
            return res
        stack=[root]
        while stack:
            node=stack.pop()
            res.append(node.val)
            if node.left:
                stack.append(node.left)
            if node.right:
                stack.append(node.right)
        #反转结果
        return res[::-1]
 
#解法2 记录走过的路径
def postorderTraversal(self, root: TreeNode) -> List[int]:
        res=[]
        if root==None:
            return res
        stack=[root]
        km=set()
        while stack:
            node=stack[-1]
            #只有叶子节点和左右节点被遍历过的才可以输出
            if (node.left==None and node.right==None) or (node.left in km or node.right in km):
                res.append(node.val)
                km.add(node)
                stack.pop()
            else:
                #注意进栈顺序
                if node.right:
                    stack.append(node.right)
                if node.left:
                    stack.append(node.left)
        return res
 
#解法3 中序遍历的变形,左右子树遍历切换
def postorderTraversal(self, root: TreeNode) -> List[int]:
        res=[]
        if root==None:
            return res
        stack=[]
        curr=root
        last=None
        while curr or stack:
            if curr:
                stack.append(curr)
                #切换到左子树
                curr=curr.left
            else:
                node=stack[-1]
                #是否切换到右子树
                if node.right and node.right!=last:
                    curr=node.right
                else:
                    res.append(node.val)
                    stack.pop()
                    last=node
        return res

  

后序遍历的几种迭代解法非常值得一看,想起当初在白板面前呆了半天也没写出来,蓝廋香菇,现在可以自信地讲树的遍历我掌握了!!!

 

posted @   msp的昌伟哥哥  阅读(443)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
点击右上角即可分享
微信分享提示