如何写出容易理解的二叉树遍历

Posted by beforeold on October 8, 2021

背景

二叉树的遍历是学习二叉树数据结构的基础,不过学完之后,如果理解不深,是特别容易忘记的,查看了很多种实现后,整理了一些相对较为容易的写法。

其中: DFS:深度优先遍历,包括前序遍历,中序遍历,后序遍历 BFS:深度优先遍历,包括层序遍历

实现

首先定义一个基础的二叉树结点。

public class TreeNode {
  public var val: Int
  public var left: TreeNode?
  public var right: TreeNode?
  public init(_ val: Int) {
    self.val = val
  }
}

前序遍历

前序遍历,是指根结点在前,子树在后。 解决思路: 使用栈结构辅助

  • 根结点先入栈
  • 访问根节点,将右结点先入栈,然后左结点再入栈
  • 循环到栈为空
func preorderTranversal(_ root: TreeNode?) -> [Int] {
  guard let root = root else {
    return []
  }
  
  var ret = [Int]()
  var stack = [TreeNode]()
  stack.append(root)
  
  while !stack.isEmpty {
    let popped = stack.removeLast()
    ret.append(popped.val)
    
    if let right = popped.right {
      stack.append(right)
    }
    
    if let left = popped.left {
      stack.append(left)
    }
  }
  
  return ret
}

中序遍历

中序遍历,是指先遍历左子树,再遍历根结点,最后变量右子树。 思路: 使用栈结构辅助

  • 一直向左直到最左的子结点,将所有的结点入栈
  • 当到达最左结点时,pop 出栈进行访问
  • 使用 pop 出栈元素的右子树,循环操作,直到栈为空
func inorderTraversal(_ root: TreeNode?) -> [Int] {
  var stack = [TreeNode]()
  var ret = [Int]()
  var node = root
  
  while node != nil || !stack.isEmpty {
    while node != nil {
      stack.append(node!)
      node = node?.left
    }
    node = stack.removeLast()
    ret.append(node!.val)
    node = node?.right
  }
  
  return ret
}

后序遍历

后序遍历,是指先遍历左右子树,再访问根节点,左右中 解题思路: 左右中,反过来就是中右左,而中右左,相当于就是前序遍历了。

1
2
3
     1
  2     3
4   5

后序(左右中):4 5 2 3 1 前序(中右左):1 3 2 5 4

步骤:

  • 前序遍历(中右左)
  • 反转结果数组

func postOrderTranversal(_ root: TreeNode?) -> [Int] {
  guard let root = root else {
    return []
  }
  
  var stack = [TreeNode]()
  var ret = [Int]()
  stack.append(root)
  
  while stack.count > 0 {
    let popped = stack.removeLast()
    ret.append(popped.val)
    
    if let left = popped.left {
      stack.append(left)
    }
    
    if let right = popped.right {
      stack.append(right)
    }
  }
  
  return ret.reversed()
}

层序遍历

层序遍历,是指根据树的层级进行遍历,因此遍历返回值是二维数组。 思路: 用队列结构辅助。

  • 首层元素入队
  • 遍历该层的元素(队列的 size),并让左右子结点依次入栈
  • 直到队列为空

func levelTranversal2(_ root: TreeNode?) -> [[Int]] {
  var ret = [[Int]]()
  var queue = [TreeNode]()
  
  if let root = root {
    queue.append(root)
  }
  
  while queue.count > 0 {
    let size = queue.count
    var level = [Int]()
    
    for _ in 0..<size {
      let dequeue = queue.removeFirst()
      level.append(dequeue.val)
      
      if let left = dequeue.left {
        queue.append(left)
      }
      
      if let right = dequeue.right {
        queue.append(right)
      }
    }
    
    ret.append(level)
  }
  
  return ret
}

小结

便于理解的思路更容易加深对学习的理解和兴趣,出于性能的考虑其实可以探索诸如 Morris 二叉树遍历的实现,称得上是“算法之美”。