【LeetCode】二叉树的中序遍历(非递归形式:栈模拟递归,标记模拟递归,莫里斯遍历)
二叉树的中序遍历
题目链接:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/
面试的时候问这道题基本都是考察非递归的写法,但还是贴一下递归写法:
方法1:递归
var result []int
func f(root *TreeNode) {
if root==nil{
return
}
f(root.Left)
result=append(result,root.Val)
f(root.Right)
}
func inorderTraversal(root *TreeNode) []int {
result=[]int{}
f(root)
return result
}
时间复杂度:O(N)
空间复杂度:O(N)
方法2:采用栈模拟递归
递归其实也有一个栈,要求非递归写法的话我们可以模拟这个栈
func inorderTraversal(root *TreeNode) []int {
var stack []*TreeNode
var result []int
// 中序遍历,非递归形式
for root != nil || len(stack) > 0 {
// 当前节点不空,则一直将当前节点的左子树入栈
if root!=nil{
stack=append(stack,root)
root=root.Left
}else {
// 当前节点空,则取栈顶元素并打印
node:= stack[len(stack)-1]
stack = stack[:len(stack)-1]
result = append(result, node.Val)
// 然后当前元素指向栈顶元素的右子树
root = node.Right
}
}
return result
}
时间复杂度:O(N)
空间复杂度:O(N)
方法3:采用标记法模拟递归
有点类似golang的gc的三色标记法
对每个节点,都需要入栈标记两次才能访问其元素值,第一次入栈是不能访问其值的,因为第一次入栈是第一次访问该节点,需要先访问该节点的左子树,本身节点,右子树分别入栈,第二次访问时,才访问其元素值
func inorderTraversal(root *TreeNode) []int {
var stack []*TreeNode
var result []int
if root == nil {
return result
}
// 中序遍历,非递归形式 标记法模拟递归
flagMap := make(map[*TreeNode]int)
stack = append(stack, root)
flagMap[root] = 1
for len(stack) > 0 {
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]
flag := flagMap[node]
// 第一次入栈元素 按照 右 自身 左 的顺序入栈,因为栈的性质,要求中序,最先打印的最后入
if flag == 1 {
// 右孩子,标记为第一次入栈
if node.Right != nil {
stack = append(stack, node.Right)
flagMap[node.Right] = 1
}
// 自身 标记为第二次入栈
stack = append(stack, node)
flagMap[node]++
// 左孩子 标记为第一次入栈
if node.Left != nil {
stack = append(stack, node.Left)
flagMap[node.Left] = 1
}
} else if flag == 2 {
// 已经是第二次入栈的元素了,直接打印
result = append(result, node.Val)
}
}
return result
}
方法4:莫里斯遍历
递归,迭代和模拟发方式都使用了额外的辅助空间,而莫里斯遍历的优点就是没有使用任何辅助空间,缺点就是将二叉树变成了链表结构
根据中序遍历的特点,将树转化为一个链表
在中序遍历中,根节点的前一个字符肯定是其左子树中最右边的那个节点
- 将黄色部分挂到5的右子树上
- 将2和5挂到4的右子树上
这样整棵树基本上就变成了一个链表结构,遍历即可,结构即是中序遍历结果
时间复杂度:O(N)
空间复杂度:O(1)
func inorderTraversal(root *TreeNode) []int {
var result []int
if root == nil {
return result
}
for root!=nil{
// 如果左节点不为空,则将当前节点连带右子树全部挂到 左节点的最右子树下面
if root.Left!=nil{
// pre.right 就是左节点的最右子树
pre:=root.Left
for pre.Right!=nil{
pre=pre.Right
}
// 挂上去
pre.Right=root
// 将root指向原来root的left
node:=root
root=root.Left
node.Left=nil
}else {
// 左子树为空,打印节点,并向右遍历
result=append(result,root.Val)
root=root.Right
}
}
return result
}
心之所向,素履以往
分类:
LeetCode
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南