[路飞]前端算法——数据结构篇(三、树): 二叉搜索树

249 阅读4分钟

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势;所以应用十分广泛,例如在文件系统和数据库系统一般会采用这种数据结构进行高效率的排序与检索操作。 [1] 

前言

前端算法系列是我对算法学习的一个记录, 主要从常见算法数据结构算法思维常用技巧几个方面剖析学习算法知识, 通过LeetCode平台实现刻意练习, 通过掘金和B站的输出来实践费曼学习法, 我会在后续不断更新优质内容并同步更新到掘金、B站和Github, 以记录学习算法的完整过程, 欢迎大家多多交流点赞收藏, 让我们共同进步, daydayup👊

目录地址:目录篇

相关代码地址: Github

相关视频地址: 哔哩哔哩-百日算法系列

I、定义

名称:

二叉排序树、二叉搜索树

规则:

  1. 左子树 < 根节点
  2. 右子树 > 根节点
  3. 左子树和右子树都是一个二叉叉找树

特性:

  1. 将二叉搜索树按中序遍历后就是一个有序的数组
  2. 利用二叉搜索树搜索可以将算法的时间复杂度稳定在 O(logn)

图例:

image.png

II、插入操作

步骤

  1. 和根节点比较
    • 如果比根节点大, 和右节点比较
    • 如果比根节点小, 和左节点比较
  2. 重复以上的操作, 当比较对象为null时,插入对应的位置

图解

b2c7a63a89dffad4d821a4679a23557d.gif

III、删除操作

1、删除叶子节点

直接删除节点即可, (断绝父子关系)
找到待删除节点的根节点, 将指向待删除节点的指针置空

2、删除出度为1的节点

把子节点过继给父节点, (自己跑出去打工, 把孩子留给爷爷奶奶照顾)
找到待删除节点的根节点与左(右)子树, 将指向待删除节点的指针指向左(右)子树

3、删除出度为2的节点

关于二叉树的前驱和后继:
将一个二叉树按中序遍历之后, 
排在当前节点之前的就是节点的前驱, 
排在当前节点之后的就是当前节点的后继, 
在二叉树中前驱是当前节点左子树的最大值, 即左子树向右查找
在二叉树中后继是当前节点右子树的最小值, 即右子树向左查找
    
需要注意, 前驱是没有右子树的, 后继是没有左子树的, 所以前驱和后继的出度为0或1
    

巧用前驱与后继

当我们得到了待删除节点的前驱和后继之后,
我们可以任意选择前驱或者后继来替换掉要删除的节点,
替换之后我们就将删除度为2的节点的问题转化为删除前驱或后继的问题(度为01),

需要注意, 前驱与后驱是中序遍历后节点的左右两边的值, 因此删除任何一个,并不影响二叉树的性质

VI、代码实现

// 构造函数
function Node(val, left, right) {
    this.val = val || 0
    this.left = left || null
    this.right = right || null
    this.height = 0 // 树高
}

// 创建
Node.prototype.getNewNode = function (val) {
    let p = new Node(val)
    p.height = 1
    return p
}

// 插入
Node.prototype.insert = function (root, target) {
    if (root === null) return this.getNewNode(target)
    if (root.val === target) return root
    if (root.val > target) {
        root.left = this.insert(root.left, target)
    } else {
        root.right = this.insert(root.right, target)
    }
    updateHeight(root)
    return root
}

// 删除
Node.prototype.earse = function (root, target) {
    if (root === null) return root
    if (root.val > target) {
        root.left = this.earse(root.left, target)
    } else if (root.val < target) {
        root.right = this.earse(root.right, target)
    } else {
        if (!root.left && !root.right) { // 出度为0
            return null
        } else if (!root.left || !root.right) { // 出度为1
            return root.left || root.right
        } else { // 出度为2
            // 获取前驱、替换、删除前驱
            let pre = this.getPre(root.left)
            root.val = pre.val
            // fix: 在左子树中删除前驱节点
            root.left = this.earse(root.left, root.val)
        }
    }
    return root
}

// 清理
Node.prototype.clear = function (root) {
    if (root === null) return
    this.clear(root.left)
    this.clear(root.right)
    return
}

// 获取前驱
Node.prototype.getPre = function (root) {
    let temp = root
    while(temp.right) {
        temp = temp.right
    }
    return temp
}

// 更新树高
function updateHeight(root) {
    root.height = Math.max(root.left.height, root.right.height) + 1
}

下一篇: [路飞]前端算法——数据结构篇(三、树): 强迫症的杰作AVL树