二叉树深度优先遍历(js)

我心飞翔 分类:javascript

二叉树深度优先遍历

概念

根据节点本身后代节点的遍历先后关系可以分为:

  1. 先序遍历: 优先遍历节点本身,然后去遍历后代节点.
  2. 中序遍历: 先遍历左侧后代节点,然后遍历节点本身,然后遍历右侧后代节点.
  3. 后序遍历: 优先遍历后代节点,然后遍历节点本身.

中序遍历

                   1
              /         \
         2                  3
       /   \              /    \
    4        5         6         7
  /  \     /   \     /   \     /   \
 8    9   10   11   12    14  15    16

中序遍历的特性: 先遍历左侧后代节点,然后遍历节点本身,然后遍历右侧后代节点.
左侧优先
1 -> 2 -> 4 -> 8 (8最先被遍历)
[8]
递推父级
8 -> 4
[8,4]
递推右侧
4 -> 9
[8,4,9]
回归曾父级
9 -> (4) -> 2
[8,4,9,2]
左侧优先
2 -> 5 -> 10
[8,4,9,2,10]
递推父级
10 -> 5
[8,4,9,2,10,5]
递推右侧
5 -> 11
[8,4,9,2,10,5,11]
回归曾父级
11 -> (2) -> 1
[8,4,9,2,10,5,11,1]
左侧优先
1 -> 3 -> 6 -> 12
[8,4,9,2,10,5,11,1,12]
递推父级
12 -> 6
[8,4,9,2,10,5,11,1,12,6]
递推右侧
6 -> 14
[8,4,9,2,10,5,11,1,12,6,14]
回归曾父级
14 -> (6) -> 3
[8,4,9,2,10,5,11,1,12,6,14,3]
左侧优先
3 -> 7 -> 15
[8,4,9,2,10,5,11,1,12,6,14,3,15]
递推父级
15 -> 7
[8,4,9,2,10,5,11,1,12,6,14,3,15,7]
递推右侧
7 -> 16
[8,4,9,2,10,5,11,1,12,6,14,3,15,7,16]
结束
....

中序遍历的一个引申含义就是`左侧优先`,
所以数组的第一个节点,就是最左侧的叶子节点
 

先序遍历

                   1
              /         \
         2                  3
       /   \              /    \
    4        5         6         7
  /  \     /   \     /   \     /   \
 8    9   10   11   12    14  15    16
先序遍历的特性: 优先遍历节点本身,然后去遍历后代节点.
自身优先
1
[1]
左边次之
1 -> 2
[1,2]
自身优先
左边次之
2 -> 4
[1,2,4]
自身优先
左边次之
4 -> 8
[1,2,4,8]
自身优选
左边次之(null)
父级提升
右边最后
4 -> 9
[1,2,4,8,9]
父级提升
自身优先
2 -> 5
[1,2,4,8,9,5]
...
 

后续遍历

                   1
              /         \
         2                  3
       /   \              /    \
    4        5         6         7
  /  \     /   \     /   \     /   \
 8    9   10   11   12    14  15    16
后序遍历的特性: 优先遍历后代节点,然后遍历节点本身.
左边优先 右边次之 自身最后
左边优先
1 -> 2 -> 4 -> 8
[8]
右边次之
8 -> (4) -> 9
[8,9]
自身最后
9 -> 4
[8,9,4]
...
 

二叉树序列化和反序列化

广度优先原则实现

  • 序列化
const serialize = (root) => {
  /**
   * queue操作原理:
   * queue.shift 弹出 root
   * queue.push 导入 left right
   * res操作原理:
   * res.push 导入 val 
   */
  const queue = [root];
  let res = [];
  while (queue.length) {
    const node = queue.shift();
    if (node) {
      res.push(node.val);
      queue.push(node.left)
      queue.push(node.right);    
    } else {
      res.push('X');
    }
  }
  return res.join(',');
}
 
  • 反序列化
const deserialize = (data) => {
  /** 排空 */
  if (data == 'X') return null;
  /** 拆分 */
  const list = data.split(',');
  /** 
   * 初始化root queue cursor
   * queue.shift() 弹出 node 并给node赋予leftNode rightNode (之所以需要参数是怕反复赋值)
   * queue.push(leftNode)(rightNode) 收集 node 供下次赋值
   * cursor 我们使用队列操作减少计算复杂度,使用索引. 从第二个元素开始,每次移动两步.
   */
  const root = new TreeNode(list[0]);
  const queue = [root];
  let cursor = 1; // 第一个元素已经存在,所以现在只想第二个元素

  while (cursor < list.length) {
    const node = queue.shift();
    /** 注意: 不要变量提升 */
    const leftVal = list[cursor];
    

    if (leftVal != 'X') {
      /** 注意区分节点和节点的值 */
      const leftNode = new TreeNode(leftVal);
      node.left = leftNode;
      queue.push(leftNode); // 放到队列里面等待等到后续调用的时候
    }

    const rightVal = list[cursor + 1];

    if (rightVal != 'X') {
      const rightNode = new TreeNode(rightVal);
      node.right = rightNode;
      queue.push(rightNode);
    }

    cursor += 2;
  }
  return root;
};
 

深度优先原则实现

  • 序列化
/** 
 * 先序遍历
 */
const serialize = (root) => {
  /** 递归边界 */
  if (root == null) {
    return 'X';
  }
  /** 递归(记得写const) */
  const left = serialize(root.left);
  const right = serialize(root.right);
  /** 拼接 */
  return root.val + ',' + left + ','+ right;
};
 
  • 反序列化
const deserialize = (data) => {
  /** 拆解 */
  const list = data.split(',');

  const buildTree = (list) => {
    /** 弹出作为root.val */
    const rootVal = list.shift();
    /** 如果是X,则返回null */
    if (rootVal == "X") {
      return null;
    }
    /** 如果不是X,则返回一个节点 */
    const root = new TreeNode(rootVal);
    root.left = buildTree(list);
    root.right = buildTree(list);
    return root;
  };

  return buildTree(list);
};
 

回复

我来回复
  • 暂无回复内容