二叉树的遍历


1. 二叉树介绍

在计算机科学中,二叉树(Binary tree)是每个节点最多只有两个分支(即不存在分支度大于 2 的节点)的树结构。通常分支被称作“左子树”或“右子树”。二叉树的分支具有左右次序,不能随意颠倒。二叉树通常作为数据结构应用,典型用法是对节点定义一个标记函数,将一些值与每个节点相关系。这样标记的二叉树就可以实现二叉搜索树和二叉堆,并应用于高效率的搜索和排序。

二叉树是一种常见的数据结构,关于对二叉树的操作无外乎就是对它进行遍历。二叉树的遍历有前序、中序、后序、层序四种遍历方式,其中前三种也称为深度优先遍历,层序遍历也称为广度优先遍历。

Binary tree

2. 深度优先遍历

深度优先遍历通常有递归和迭代两种解法。以一个题为例:给定一个二叉树的根节点 root ,以数组的形式返回它的 前、中、后 遍历的结果。

示例

前序遍历结果:[1, 2, 3]
中序遍历结果:[1, 3, 2]
后序遍历结果:[3, 2, 1]

2.1 递归解法

/**
 * Definition for a binary tree node.
 * class TreeNode {
 *     val: number
 *     left: TreeNode | null
 *     right: TreeNode | null
 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.left = (left===undefined ? null : left)
 *         this.right = (right===undefined ? null : right)
 *     }
 * }
 */

function preorderTraversal(root: TreeNode | null): number[] {
    // 递归,时间O(n), 空间O(n)
    const res: number[] = []
    function visit(node: TreeNode): void{
        res.push(node.val)
    }
    function dfs(node: TreeNode | null): void{
        if(node === null)   return
        // 前序遍历写在这
        visit(node)
        dfs(node.left)
        // 中序遍历写在这
        // visit(node)
        dfs(node.right)
        // 后序遍历写在这
        // visit(node)
    }
    dfs(root)
    return res
}

对于前序、中序和后序遍历,只需将递归函数里的 visit 函数放在不同位置即可,其他的代码完全一样。

2.2 迭代解法

2.2.1 前序遍历

function preorderTraversal(root: TreeNode | null): number[] {
    // 迭代
    function visit(node: TreeNode): void{
        res.push(node.val)
    }
    const res: number[] = []
    // 初始化栈
    const stack: TreeNode[] = []
    // cur是遍历指针
    var cur: TreeNode = root
    while(cur !== null || stack.length !== 0){
        while(cur !== null){
            // 将根节点 cur 和所有的左孩子入栈并加入结果中,直至 cur 为空
            visit(cur)
            stack.push(cur)
            cur = cur.left
        }
        // 每弹出一个栈顶元素 temp,就到达它的右孩子
        var temp: TreeNode = stack.pop()
        cur = temp.right
    }
    return res
}

2.2.2 中序遍历

和前序遍历的代码完全相同,只是在出栈的时候才将节点 temp 的值加入到结果中。

function inorderTraversal(root: TreeNode | null): number[] {
    function visit(node: TreeNode): void{
        res.push(node.val)
    }
    const res: number[] = []
    const stack: TreeNode[] = []
    var cur: TreeNode = root
    while(cur !== null || stack.length !== 0){
        while(cur !== null){
            stack.push(cur)
            cur = cur.left
        }
        var temp: TreeNode = stack.pop()
        visit(temp)
        cur = temp.right
    }
    return res
}

2.2.3 后序遍历

继续按照上面的思想,这次我们反着思考,节点 cur 先到达最右端的叶子节点并将路径上的节点入栈;然后每次从栈中弹出一个元素后,cur 到达它的左孩子,并将左孩子看作 cur 继续执行上面的步骤。最后将结果反向输出即可。

function postorderTraversal(root: TreeNode | null): number[] {
    function visit(node: TreeNode): void{
        res.push(node.val)
    }
    const res: number[] = []
    const stack: TreeNode[] = []
    var cur: TreeNode = root
    while(cur !== null || stack.length !== 0){
        while(cur !== null){
            // 先到达最右端
            visit(cur)
            stack.push(cur)
            cur = cur.right
        }
        // 每弹出一个栈顶元素 temp,就到达它的左孩子
        var temp: TreeNode = stack.pop()
        cur = temp.right
    }
    return res.reverse() // 反向输出
}

3. 广度优先遍历

二叉树的广度优先遍历一般采用迭代方法,与前面深度优先遍历的迭代方法实现有所不同。前面的迭代方法都使用栈实现,而广度优先遍历使用队列实现。以一个题为例:给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

示例:
二叉树:

示例

返回其层序遍历结果:
[ [3], [9,20], [15,7] ]

广度优先遍历的步骤为:

  1. 初始化队列 q,并将根节点 root 加入到队列中;
  2. 当队列不为空时:
    队列中弹出节点 node,加入到结果中;
    如果左子树非空,左子树加入队列;
    如果右子树非空,右子树加入队列;
/**
 * Definition for a binary tree node.
 * class TreeNode {
 *     val: number
 *     left: TreeNode | null
 *     right: TreeNode | null
 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.left = (left===undefined ? null : left)
 *         this.right = (right===undefined ? null : right)
 *     }
 * }
 */

function levelOrder(root: TreeNode | null): number[][] {
    if (root == null) {
        return []
    }
    let result: number[][] = []
    let queue: TreeNode[] = [root]
    while (queue.length) {
        // 每一层的节点数
        let level: number = queue.length
        let currLevel: number[] = []
        // 每次遍历一层元素
        for (let i = 0; i < level; i++) {
            // 当前访问的节点出队
            let curr: TreeNode = queue.shift()
            // 出队节点的子女入队
            if (curr.left) queue.push(curr.left)
            if (curr.right) queue.push(curr.right)
            currLevel.push(curr.val)
        }
        result.push(currLevel)
    }
    return result
};

文章作者: Cubby
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Cubby !
  目录