Skip to content

概念

  • 一种分层数据的抽象模型。
  • 前端中常见的树包括: DOM树、级联选择、树形控件.....
  • JS 中没有树,可以用 Object 和 Array 构建树。
  • 树的常用操作:深度/广度优先遍历、先中后序遍历。

深度/广度优先遍历

  • 深度优先遍历:尽可能深的搜索树的分支。
  • 广度优先遍历:先访问里根节点最近的节点。

深度优先遍历算法口诀

  • 访问根节点。
  • 对根节点的 children 挨个进行深度优先遍历。

广度优先遍历算法口诀

  • 新建一个队列,把根节点入队。
  • 把对头出队并访问。
  • 把对头的children挨个入队。
  • 重复第二、三步,直到队列为空。

二叉树的先中后序遍历

二叉树是什么?

  • 树中每个节点最多只能有两个子节点。
  • 在 JS 中通常用 Object来模拟二叉树。

先序遍历算法口诀

  • 访问节点。
  • 对根节点的子树进行先序遍历。
  • 对根节点的子树进行先序遍历。
javascript
const bt = {
  val: 1,
  left: {
    val: 2,
    left: {
      val: 4,
      left: null,
      right: null,
    },
    right: {
      val: 5,
      left: null,
      right: null,
    },
  },
  right: {
    val: 3,
    left: {
      val: 6,
      left: null,
      right: null,
    },
    right: {
      val: 7,
      left: null,
      right: null,
    },
  },
};

const preOrder = (root) => {
  if (!root) return;
  console.log(root.val);
  preOrder(root.left);
  preOrder(root.right);
};

preOrder(bt);
// 1
// 2
// 4
// 5
// 3
// 6
// 7

中序遍历算法口诀

  • 对根节点的子树进行中序遍历。
  • 访问节点。
  • 对根节点的子树进行中序遍历。
javascript
中序遍历
const inOrder = (root) => {
  if (!root) return;
  inOrder(root.left);
  console.log(root.val);
  inOrder(root.right);
};

inOrder(bt); //4251637

后序遍历算法口诀

  • 对根节点的子树进行后序遍历。
  • 对根节点的子树进行后续遍历。
  • 访问节点。
javascript
const postOrder = (root) => {
  if (!root) return;
  postOrder(root.left);
  postOrder(root.right);
  console.log(root.val);
};


postOrder(bt); // 4526731

二叉树的先中后序遍历(非递归版)

先序遍历

javascript
const preOrder = (root) => {
  if (!root) return;
  const stack = [root];
  while (stack.length) {
    const n = stack.pop();
    console.log(n.val);
    if (n.right) stack.push(n.right);
    if (n.left) stack.push(n.left);
  }
};

preOrder(bt);
// 1
// 2
// 4
// 5
// 3
// 6
// 7

中序遍历

javascript
const inOrder = (root) => {
  if (!root) return;
  const stack = [];
  let p = root;
  while (stack.length || p) {
    while (p) {
      stack.push(p);
      p = p.left;
    }
    const n = stack.pop();
    console.log(n.val);
    p = n.right;
  }
};

inOrder(bt); //4251637

后序遍历

javascript
const postOrder = (root) => {
  if (!root) return;
  const outputStack = [];
  const stack = [root];
  while (stack.length) {
    const n = stack.pop();
    outputStack.push(n); // 先存储
    if (n.left) stack.push(n.left);
    if (n.right) stack.push(n.right);
  }
  while (outputStack.length) {
    const n = outputStack.pop();
    console.log(n.val);
  }
};

postOrder(bt); // 4526731

LeetCode:104.二叉树的最大深度

解题思路

  • 求最大深度,考虑使用深度优先遍历。
  • 在深度优先遍历过程中,记录每个节点所在的层级,找出最大层级即可。

解题步骤

  • 新建一个变量,记录最大深度
  • 深度优先遍历整棵树,并记录每个节点的层级,同步不断刷新最大深度这个变量。
  • 遍历结束后返回最大深度这个变量。
javascript
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    debugger
    let res = 0;
    // 深度优先遍历 
    const dfs = (n, l) => {
        if (!n) return;

        if (!n.left && !n.right) {
            res = Math.max(res, l)
        }

        dfs(n.left, l + 1);
        dfs(n.right, l + 1);
    }

    dfs(root, 1)
    return res
};

// 时间复杂度 O(n) 遍历了树一遍
// 空间负责度 O(logn) ~ O(n)

递归的方式

javascript
var maxDepth = function(root) {
    if (root === null) {
        return 0
    }
    // 树最大深度,等于左子树的深度和右子树的深度中较大的那个+1
    return Math.max(maxDepth(root.left), maxDepth( root.right)) + 1
};

LeetCode:111.二叉树的最小深度

解题思路

  • 求最小深度,考虑使用广度优先遍历。
  • 在广度优先遍历中,遇到叶子节点,停止遍历,直接返回节点层级。

解题步骤

  • 广度优先遍历整棵树,并记录每个节点的层级。
  • 遇到叶子节点,返回节点层级,并停止遍历。
javascript
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var minDepth = function(root) {
    if (!root) return 0;
    const q = [[root, 1]];
    // 广度优先
    while (q.length) {
        const [n , l] =  q.shift()
        if (!n.left && !n.right) {
            return l
        }
        if (n.left) q.push([n.left, l + 1])
        if (n.right) q.push([n.right, l + 1])
    }

};

// 时间复杂度 O(n) 最大n是节点的数量
// 空间负责度 O(n) 最大n是节点的数量

LeetCode:102.二叉树的层序遍历

解题思路

  • 层序遍历顺序就是广度优先遍历。
  • 遍历时需要记录当前节点所处的层级,方便将其添加到不同的数组中。

解题步骤

  • 广度优先遍历
  • 遍历过程中,记录每个节点嗯层级,并将其添加到不同的数组中。
javascript
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrder = function(root) {
    if (!root) return [];
    const q = [[root, 0]]
    const res = []
    while (q.length) {
        const [n ,level]  = q.shift()
        if (!res[level]) {
            res.push([n.val])
        } else {
            res[level].push(n.val)
        }
        if (n.left) q.push([n.left, level + 1])
        if (n.right) q.push([n.right, level + 1])
    }
    return res
};


// 遍历时只需要知道每层的个数,就可以一次性push了
var levelOrder = function(root) {
    if (!root) return []
    const q = [root]
    const res = []
    while (q.length) {
        let len = q.length // len 为每层的节点
        res.push([])
        while (len--) {
            const n = q.shift()
            res[res.length -1].push(n.val)
            if (n.left) q.push(n.left)
            if (n.right) q.push(n.right)
        }
    }
    return res
};
// 时间复杂度 O(n)
// 空间复杂度 O(n)

LeetCode:94.二叉树的中序遍历

javascript
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
// 递归版
var inorderTraversal = function(root) {
    const res = []
    const rec = (n) => {
        if (!n) return;
        rec(n.left);
        res.push(n.val)
        rec(n.right)
    }
    rec(root)
    return res
};
// 遍历的方式
var inorderTraversal = function(root) {
    const res = []
    const stack = []
    let p = root
    while (stack.length || p) {
        while (p) {
            stack.push(p)
            p = p.left
        }
        const n = stack.pop()
        res.push(n.val)
        p = n.right
    }
    return res
};
// 时间和空间复杂度都是O(n)

LeetCode:112.路径总和

解题思路

  • 在深度优先遍历的过程中,记录当前路径的节点值的和。
  • 在叶子节点处,判断当前路径的节点值的和是否等于目标值。

解题步骤

  • 深度优先遍历二叉树,在叶子节点处,判断当前路径的节点值的和是否等于目标值,是就返回true。
  • 遍历结束,如何没有匹配,就返回false。
javascript
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} targetSum
 * @return {boolean}
 */
var hasPathSum = function(root, targetSum) {
    if (!root) return false;
    let res = false
    const dfs = (n, s, x) => {
        if (!n.left && !n.right && s === targetSum) {
            res = true
        }
        if (n.left) dfs(n.left, s + n.left.val);
        if (n.right) dfs(n.right, s + n.right.val);
    }
    dfs(root, root.val)
    return res
};

// 时间复杂度 O(n)
// 空间复杂度 O(n) n是递归堆栈的高度

226. 翻转二叉树

javascript
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function(root) {
    if (root === null) {
        return null
    }
    // 递归子问题
    [root.left, root.right] = [invertTree(root.right), invertTree(root.left)]
    return root
};