二叉树的前序中序后序遍历
二叉树的前序遍历
根节点——左子树——右子树的方式遍历二叉树。
方法1:递归
func preorderTraversal(root *TreeNode) (vals []int) { var preorder func(*TreeNode) preorder = func(node *TreeNode) { if node == nil { return } vals = append(vals, node.Val) preorder(node.Left) preorder(node.Right) } preorder(root) return }
方法2:迭代
使用一个栈来模拟迭代的过程。首先将根节点入栈,然后在循环中,从栈中弹出一个节点并将其值添加到结果列表中。接着,如果存在右子节点,将右子节点入栈;如果存在左子节点,将左子节点入栈。通过这种方式,我们可以实现前序遍历的顺序。
func preorderTraversal(root *TreeNode) []int {
var result []int
if root == nil {
return result
}
stack := []*TreeNode{root}
for len(stack) > 0 {
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]
result = append(result, node.Val)
if node.Right != nil {
stack = append(stack, node.Right)
}
if node.Left != nil {
stack = append(stack, node.Left)
}
}
return result
}
复杂度分析
时间复杂度:O(n)O(n),其中 nn 是二叉树的节点数。每一个节点恰好被遍历一次。
空间复杂度:O(n)O(n),为迭代过程中显式栈的开销,平均情况下为 O(\log n)O(logn),最坏情况下树呈现链状,为 O(n)O(n)。
二叉树的中序遍历
按照访问左子树——根节点——右子树的方式遍历这棵树
方法1:递归版本
func inorderTraversal(root *TreeNode) (res []int) { var inorder func(node *TreeNode) inorder = func(node *TreeNode) { if node == nil { return } inorder(node.Left) res = append(res, node.Val) inorder(node.Right) } inorder(root) return }
方法2:迭代版本
使用一个栈来模拟迭代的过程。首先,将根节点及其所有左子节点依次入栈,直到遍历到最左的叶子节点。然后,从栈中弹出一个节点,并将其值添加到结果列表中。接着,处理弹出节点的右子节点,重复上述过程,直到所有节点都被遍历。
func inorderTraversal(root *TreeNode) []int {
var result []int
if root == nil {
return result
}
stack := []*TreeNode{}
node := root
for node != nil || len(stack) > 0 {
// 将当前节点及其左子节点入栈
for node != nil {
stack = append(stack, node)
node = node.Left
}
// 弹出栈顶节点,将其值添加到结果列表中
node = stack[len(stack)-1]
stack = stack[:len(stack)-1]
result = append(result, node.Val)
// 处理右子节点
node = node.Right
}
return result
}
复杂度分析
时间复杂度:O(n)O(n),其中 nn 为二叉树节点的个数。二叉树的遍历中每个节点会被访问一次且只会被访问一次。
空间复杂度:O(n)O(n)。空间复杂度取决于栈深度,而栈深度在二叉树为一条链的情况下会达到 O(n)的级别。
二叉树的后序遍历
按照访问左子树——右子树——根节点的方式遍历这棵树。
方法1:递归版本
func postorderTraversal(root *TreeNode) (res []int) { var postorder func(*TreeNode) postorder = func(node *TreeNode) { if node == nil { return } postorder(node.Left) postorder(node.Right) res = append(res, node.Val) } postorder(root) return }
方法2:迭代版本
首先,将根节点及其所有左子节点依次入栈,直到遍历到最左的叶子节点。然后,检查栈顶节点的右子节点。如果右子节点存在且未被访问过,则将右子节点设为当前节点,继续入栈左子节点。如果右子节点不存在或已被访问过,则弹出栈顶节点,并将其值添加到结果列表中。重复这个过程,直到栈为空。
func postorderTraversal(root *TreeNode) []int {
var result []int
if root == nil {
return result
}
stack := []*TreeNode{}
var prev *TreeNode // 用于记录上一个访问的节点
for root != nil || len(stack) > 0 {
// 将当前节点及其左子节点依次入栈
for root != nil {
stack = append(stack, root)
root = root.Left
}
// 查看栈顶节点
node := stack[len(stack)-1]
// 如果栈顶节点的右子节点存在且未被访问过,则处理右子节点
if node.Right != nil && node.Right != prev {
root = node.Right
} else {
// 否则,弹出栈顶节点并将其值添加到结果列表中
stack = stack[:len(stack)-1]
result = append(result, node.Val)
prev = node // 更新上一个访问的节点
}
}
return result
}
复杂度分析
时间复杂度:O(n)O(n),其中 nn 是二叉搜索树的节点数。每一个节点恰好被遍历一次。
空间复杂度:O(n)O(n),为迭代过程中显式栈的开销,平均情况下为 O(\log n)O(logn),最坏情况下树呈现链状,为 O(n)O(n)。