研习算法第六站-树(javascript版)

235 阅读4分钟

树简介

  • 一种分层数据的抽象模型
  • 前端常见的树包括:DOM树、级联选择、树控件
  • js中没有树,使用Object和Array模拟树
  • 树的常用操作: 深度/广度优先遍历、先中后序遍历(二叉树)

树的深度和广度优先遍历

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

image.png

深度优先遍历算法口诀

  • 访问根节点
  • 对根节点的children挨个进行深度优先遍历
const tree = {
  val: "a",
  children: [
    {
      val: "a-1",
      children: [
        {
          val: "a-1-1",
        },
        {
          val: "a-1-2",
        },
      ],
    },
    {
      val: "a-2",
      children: [
        {
          val: "a-2-1",
        },
        {
          val: "a-2-2",
        },
      ],
    },
  ],
};
const dfs = (tree) => {
  if (!tree?.val) {
    return;
  }
  console.log(tree.val);
  tree?.children?.forEach(dfs);
};
/**
a
a-1
a-1-1
a-1-2
a-2
a-2-1
a-2-2
**/
dfs(tree);

广度优先遍历口诀

  • 新建一个队列,把根节点入队
  • 把队头出队并访问
  • 把队头的children 挨个入队
  • 重复第二、三步,直到队列为空
const tree = {
  val: "a",
  children: [
    {
      val: "a-1",
      children: [
        {
          val: "a-1-1",
        },
        {
          val: "a-1-2",
        },
      ],
    },
    {
      val: "a-2",
      children: [
        {
          val: "a-2-1",
        },
        {
          val: "a-2-2",
        },
      ],
    },
  ],
};
module.exports = {
  tree,
};
const bfs = (tree) => {
  const q = [tree];

  while (q.length) {
    let n = q.shift();
    console.log(n?.val);
    n?.children?.forEach((c) => q.push(c));
  }
};
/**
a
a-1
a-2
a-1-1
a-1-2
a-2-1
a-2-2
**/
bfs(tree);

二叉树的先中序遍历

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

先序遍历口诀 (根左右)

  • 访问根节点
  • 对根节点的左子树进行先序遍历
  • 对根节点的右子树进行先序遍历 image.png
const tree = {
  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 = (tree) => {
  if(!tree) {
    return;
  }
  console.log(tree?.val);
  preOrder(tree?.left);
  preOrder(tree?.right);
}
/**
1 2 4 5 3 6 7
**/
preOrder(tree)

// 非递归版
const preOrder = (tree) => {
  if (!tree) {
    return;
  }
  const stack = [tree];
  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);
  
  }
};

中序遍历口诀 (左 根 右)

  • 对根节点的左子树进行中序遍历
  • 访问根节点
  • 对根节点的右子树进行中序遍历

image.png

const tree = {
  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 inOrder = (tree) => {
  if(!tree) {
    return;
  }
 
  inOrder(tree?.left);
   console.log(tree?.val);
  inOrder(tree?.right);
}
/**
4 2 5 1 6 3 7
**/
inOrder(tree)
// 非递归版
 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;
   }
 };

后序遍历算法口诀 (左右根)

  • 对根节点的左子树进行后序遍历
  • 对根节点的右子树进行后序遍历
  • 访问根节点

image.png

const tree = {
  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 postOrder = (tree) => {
  if(!tree) {
    return;
  }
 
  postOrder(tree?.left);
  postOrder(tree?.right);
  console.log(tree?.val);
}
/**
4 5 2 6 7 3 1
**/
postOrder(tree)
// 非递归版
const postOrder = (root) => {
  if (!root) {
    return;
  }
  const stack = [root];
  const outPutStack = [];
  while (stack.length) {
    const c = stack.pop();
    outPutStack.push(c);
    if (c.left) stack.push(c.left);
    if (c.right) stack.push(c.right);
  }
  while (outPutStack.length) {
    const c = outPutStack.pop();
    console.log(c.val);
  }
};

leetcode-cn.com 算法题实战

完整题目请打开 leetcode

image.png

解题思路

  • 求最多深度,考虑使用深度优先遍历
  • 在深度优先遍历过程中,记录每个节点所在层级,找出最大层级即可
  • 新增一个遍历记录最大深度
  • 深度优先遍历整棵树,并记录每个节点层级,同时不断刷新最大深度这个变量
/**
 * 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) {

    let res = 0;
    const dfs = (root, l) => {
    if(!root) { return; }
        if(!root?.left && !root?.right) {
        res = Math.max(res,l);
        }
       
        if(root?.left) {
            dfs(root?.left,l + 1)
        }
        if(root?.right) {
            dfs(root?.right, l + 1)
        }
       
    }
    dfs(root,1)
    return res;
};

111. 二叉树的最小深度

image.png

解题思路

  • 求最小深度,考虑使用广度优先遍历
  • 在广度优先遍历过程中,遇到叶子节点,停止遍历,返回节点层级
  • 广度优先遍历整棵树,并记录每个节点层级
  • 遇到叶子节点,返回节点层级,停止遍历
/**
 * 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]);
  }
};

下一站 图