算法随笔-二叉树遍历的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 |
递归实现极其简单,但是面试时往往会更进一步,问几个深入一点的问题:
- 递归实现存在什么问题?
- 尾递归是什么?
这两个问题都在讲一件事,就是迭代实现的二叉树遍历会存在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 |
后序遍历的几种迭代解法非常值得一看,想起当初在白板面前呆了半天也没写出来,蓝廋香菇,现在可以自信地讲树的遍历我掌握了!!!
标签:
算法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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,谁才是开发者新宠?