数据结构之二叉树

目录

一、二叉树的先序遍历

二、二叉树的中序遍历

 三、二叉树的后序遍历


先中后序公用的数据 tree:

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,
    },
  },
};

一、二叉树的先序遍历

1. 先遍历根节点;

2. 对根节点的左子树进行先序遍历;

3. 对根节点的右子树进行先序遍历。

// 先序遍历(先根再左后右)——递归版
const preorder = (root) => {
  if(!root) {return}
  // 先访问根节点的val
  console.log(root.val)
  // 再递归左子节点
  preorder(root.left)
  // 再递归右子节点
  preorder(root.right)
}

preorder(bt)
// 先序遍历(先根再左后右)——非递归版(栈)
const preorder = (root) => {
    if (!root) { return; }
    const stack = [root];
    while (stack.length) {
        // 将根节点push进去,并打印
        const n = stack.pop();
        console.log(n.val);
        // 先将右子树 push 进去,再 push 左子树,这样在 pop 时才能做到
        // 先 pop 出来左子树,其次才会是右子树
        if (n.right) stack.push(n.right);
        if (n.left) stack.push(n.left);
    }
};

preorder(bt);

打印结果:

二、二叉树的中序遍历

1. 对根节点的左子树进行中序遍历;

2. 访问根节点;

3. 对根节点的右子树进行中序遍历。

// 中序遍历(先左再根后右)——递归版
const inorder = (root) => {
  if (!root) return
  // 先递归左子树
  inorder(root.left)
  // 访问根节点 val
  console.log(root.val)
  // 后递归右子树
  inorder(root.right)
}

inorder(bt)
// 中序遍历(先左再根后右)——非递归版(栈)
const inorder = (root) => {
    if (!root) { return; }
    // 存放遍历出来的节点
    const stack = [];
    // 指针,用于指向当前根节点
    let p = root;
    // 看这个大循环之前,请先看下面的小循环解析
    // 大循环解析:第一次进来,stack 栈为空,p 为根节点,进入循环;
    // 第二次再进入大循环,此时 stack 栈里存满了整棵树的左子节点(包括根节点)
    // 且在循环体内部, 指针 p 被赋值成 栈里最新的一个左子节点的右子节点,如果该左子节点
    // 的右子节点不存在,则跳出小循环,直接把 stack 栈中最后一个左子节点 pop 出来,并访问其
    // val, 否则就进入小循环,判断其是否含有左子节点,此时进入大循环的上一个子节点的右子节点
    // 已经可以看做根节点了
    while (stack.length || p) {
        // 可以先看当前这个小循环,小循环先把根节点推入栈中,这里可以理解为根节点是一个左节点
        // 然后将根节点的左节点赋值为根节点,再进行循环,一遍一遍地将根节点的左子节点推入栈中
        // 直至树的左节点全部被推入栈中
        while (p) {
            stack.push(p);
            p = p.left;
        }
        // 把栈的最新(即末尾,最后push进去的)一项 pop 出来,并打印,
        // 此时就是最后一层左节点的值,即先打印左节点,其实也可以把 pop 出来的
        // 这个节点,当作一棵新树的根节点
        const n = stack.pop();
        console.log(n.val);
        // 因为整棵树的左节点都遍历完了,所以这棵新树也不存在左节点了,
        // 因为如果存在的话,上面的小循环是不会结束的,所以就把这棵新树
        // 的右子节点推入到栈中,此时小循环结束,开始了新的一轮大循环
        p = n.right;
    }
};

inorder(bt);

打印结果:

 三、二叉树的后序遍历

1. 对根节点的左子树进行后续遍历

2. 对根节点的右子树进行后续遍历

3. 访问根节点

// 后序遍历(先左再右后根)——递归版
const postorder = (root) => {
  // 如果根节点不存在,即为 null 或 undefined 或 0,就直接 return,
  if (!root) return
  // 然后递归左子节点
  postorder(root.left)
  // 再递归右子节点
  postorder(root.right)
  // 最后访问根节点
  console.log(root.val)
}

postorder(bt)
// 后序遍历(先左再右后根)——非递归版(栈)
// 后序遍历的顺序与先序遍历相反,那么是不是可以把先序遍历的顺序掉个个
// 先序遍历的顺序是根 -> 左 -> 右,那么栈里存的就会是 右 -> 左 -> 根
// 后序遍历的顺序是左 -> 右 -> 根,那么栈里存的就会是 根 -> 右 -> 左
const postorder = (root) => {
    if (!root) { return; }
    // 缓存新的栈,用于存放从 stack 中 pop 出来的节点
    const outputStack = [];
    // 按根、右、左的顺序将节点推入栈中
    const stack = [root];
    while (stack.length) {
        // 当 stack 中是 root 时,先 pop,即先弹出来的就是根节点
        // 然后随着下面左子节点和右子节点的顺序入栈,stack 栈内的顺序就是
        // 右 -> 左 -> 根
        // 又因为后序遍历肯定是左先出,所以在 stack 中存的应该是 左 -> 右 -> 根
        // 这样才能保证在新栈中的顺序是 根 -> 右 -> 左
        const n = stack.pop();
        // 将根节点进行入栈
        outputStack.push(n);
        
        // 因为左在右的前面先被存入栈中,所以先把左子节点推入栈中
        if (n.left) stack.push(n.left);
        // 再把右子节点推入栈中
        if (n.right) stack.push(n.right);
    }
    while(outputStack.length){
        // 最后再把新栈中的 根 -> 右 -> 左 的子节点 pop 出来,并访问
        const n = outputStack.pop();
        console.log(n.val);
    }
};

postorder(bt);

打印结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值