二叉树基础知识、递归遍历、迭代非递归遍历🎁JavaScript

77 阅读4分钟

二叉树基础

JS中没有原生的二叉树容器

  • 二叉树种类
    • 满二叉树(每层都满)只有(出)度为0和度为2的结点,且度为0的结点在最后一层,深度为k2k1=20+21+22+...+2k12^k-1=2^0+2^1+2^2+...+2^{k-1}个结点image.png
    • 完全二叉树,除了最后一层全满,最后一层结点全在最左边image.png
    • 二叉搜索树,有数值且有序:
      • 若左子树不空,左子树所有结点值小于它根结点的值
      • 若右子树不空,右子树所有结点值大于它根结点的值
      • 左右子树也为二叉排序树
      • image.png
    • 平衡二叉搜索树(AVL)一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
    • image.png
  • 二叉树存储方式:链式(指针链表、最常用)、顺序(数组、不常用)
    • 链式image.png
function TreeNode (val, left, right) { // 结点的构造函数:值,左节点指针,右节点指针
  this.val = (val === undefined ? 0 : val)
  this.left = (left === undefined ? null : left)
  this.right = (right === undefined ? null : left)
}
  • 数组:父节点数组下标i,左孩子2i+1,右孩子2i+2
  • image.png
  • 二叉树遍历方式:
    • 深度优先遍历(递归法,迭代法):先往深走,遇到叶子节点再往回走

      • 前序(中左右)
      • 中序(左中右)
      • 后序(左右中)
      • 前中后指中间节点位置
      • image.png
    • 广度优先遍历(迭代法):一层一层的去遍历

      • 层次遍历

递归遍历

递归三部曲:

  1. 确定递归函数的参数和返回值
  2. 确定终止条件
  3. 确定单层递归的逻辑
  • 参数:保存结果的数组,当前的根节点(中)
  • 递归条件:深度遍历到底root === null
  • 前中后序遍历二叉树只有递归顺序不同

先序递归遍历

/**
 * 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 preorderTraversal = function(root) { // 先序遍历
    let res =  [] // 存结果数组
    let pre = function (root) { // 递归先序遍历的函数,1.参数
        if (root === null) return // 2.如果这次遍历的根节点为空就终止
        // 3. 每层的操作
        res.push(root.val) // 中,值加入结果
        pre(root.left) // 左:递归左子树
        pre(root.right) // 右:递归右子树
    }
    pre(root) // 最开始的根节点调用先序递归函数
    return res // 利用闭包
};

后序递归遍历

var postorderTraversal = function(root) { // 后序遍历
    let res = [] // 存结果
    let back = function (root) {
        if (root === null) return
        back(root.left) // 左
        back(root.right) // 右
        res.push(root.val) // 中
    }
    back(root)
    return res
};

中序递归遍历

var inorderTraversal = function(root) {
    let res = []
    const mid = function (root) {
        if (root === null) return
        mid(root.left) // 左
        res.push(root.val)  // 中
        mid(root.right) // 右
    }
    mid(root)
    return res
};

迭代遍历

先序用栈遍历:中左右

  • 定义一个栈sack存遍历的顺序,先放入根节点root,如果根节点rootnull直接返回,防止后面操作null
  • 把父节点出栈,放入结果数组res并把右、左子树分别加入栈,出栈时顺序就正好为左右
  • 直到栈为空:存遍历过的节点的栈为空,说明所有节点都遍历完

(FA)R3W)ADXR9~AM9{W$E_tmb.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 preorderTraversal = function(root) { // 先序遍历非递归
    let res = [] // 结果数组
    if(root === null) return res // 如果为null直接返回,避免后面操作null
    let stack = [root] // 使用栈,存遍历过的节点
    while (stack.length) { // 只要栈里还有值
        let cur = stack.pop() // 弹出栈顶节点
        res.push(cur.val) // 加入结果数组
        cur.right && stack.push(cur.right)// 右左节点分别加入栈:出栈顺序正好左右
        cur.left && stack.push(cur.left)

    } // 遍历过的节点栈为空说明所有节点都遍历完
    return res
};

后序递归遍历:左右中

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 postorderTraversal = function(root) {
    let res = []
    if (root === null) return res
    let stack = [root]
    while (stack.length) {
        let cur = stack.pop()
        res.push(cur.val)
        cur.left && stack.push(cur.left) // 和先序遍历左右相反,栈先加入左,弹出的顺序就是右左
        cur.right && stack.push(cur.right) // 加入右
    }
    return res.reverse()
};

中序遍历:左中右

  • 定义指针去遍历节点,当前cur指针不为空,加入栈,一直往左边走,直到遇到左子树为空,当前cur指向弹出栈顶父元素(中)加入结果数组,指针再往右
  • 如果cur指向的右子树为空,说明它父节点代表的子树遍历完了,再从栈里弹出一个

image.pngimage.png

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 inorderTraversal = function(root) { // 非递归中序遍历:左中右
    let stack = [] // 栈记录遍历的节点:用来保存父节点,方便子树遍历完回退上来
    let cur = root // 用指针去遍历二叉树
    let res = [] // 存结果
    while (cur !== null || stack.length) { // 指针不指向空或者栈不为空,说明没遍历完
        if (cur) { // 当前节点不为空,先左子树
            stack.push(cur) // 父节点入栈
            cur = cur.left // 1.先左子树
        } else { // 指针指向的节点为空
            cur = stack.pop() // 父节点弹出来:回退到父(中)
            res.push(cur.val) // 2.中
            cur = cur.right // 3.右
        }
    }
    return res
};