JS 数据结构 —— 二叉搜索树(上篇)🚣

240 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

本篇文章主要是介绍并使用 js 自行封装一个二叉搜索树(Binary Search Tree, BST)。首先,我们来讲讲,什么是树结构。

简单介绍

说到树,可能想到的是浏览器渲染用到的 DOM 树,vue 里用到的虚拟 DOM 树等,它们都是一种非线性数据结构,按照百度百科的话说,就是数据元素(节点)按分支关系组织起来的结构,很像自然界中的树那样。

树结构的表示

普通的树结构,每个节点都可以有很多的子节点,也就是有很多的分支,如下图所示:
2022-02-03_234828.png
如果我们让上图中,每个节点都拥有两个属性,一个属性 left,指向的是该节点的所有子节点中最左边的那个;另一个属性 right,则指向该节点的右边的兄弟节点,那么上图就变成了下图所示:

yuque_diagram.jpg

这样,无论原本的树结构中,一个节点有多少个子节点,都可以转变为一个节点的度(节点的子树个数)不大于 2 的二叉树。而二叉搜索树,就是满足下列特殊条件的二叉树:

  • 非空左子树,所有键值都小于其根节点的键值;
  • 非空右子树,所有键值都大于其根节点的键值;
  • 左右子树本身也是二叉搜索树。

一言以蔽之,就是在有左右子节点的情况下,每个节点的键值必然是大于其左子节点的键值,小于其右子节点的键值。 二叉搜索树的一个特点在于,在查找某个节点时,所需要的最大次数,等于二叉搜索树的深度(规定根节点为第 1 层,其余节点为父节点的层数 + 1,然后树中所有节点里的最大层次为数的深度)。

优点&缺点

二叉搜索树可以快速地根据给定的关键字 key 查找到数据,得到数据所需要的查找次数,最多就等于树的深度。如果二叉搜索树的节点,左右分布是比较均匀的平衡树,那么插入查找的效率是 O(logN)。但是,如果插入的数据是比如 1、2、3、4、5、6、7 这样有序的,就会形成以 1 为根,只有右子树的非平衡树,其实就可以看成是一个单向链表了,其查找效率就会变成 O(N)。这里顺便提一句,如果想让生成的 BST 尽可能是棵平衡树,可以用红黑树这种符合一些规定特性的二叉搜索树结构。

代码封装

整体框架

我们先定义一个 BinarySearchTree 类用于封装一个二叉搜索树结构。它只需要一个 root 属性用于指向根节点,增删改查等方法则在后面慢慢添加。

class BinarySearchTree {
  constructor() {
    this.root = null
  }
}

另外我们还需要以 CreateNode 类用来生成节点。它有 3 个属性:

  1. key:保存节点的键值
  2. left:保存节点的左子节点的引用
  3. right:保存节点的右子节点的引用
class CreateNode {
  constructor(key) {
    this.key = key
    this.left = null
    this.right = null
  }
}

方法

增 insert()

insert() 的方法用于向二叉搜索树添加新节点,传入一个 key 作为该节点的键值。思路分析如下:

  1. 定义 insertNode 递归函数,传入两个参数 —— 新节点 newNode 和节点 node(二叉搜索树上的根节点或某个子节点),比较它两的键值大小,如果 newNode 的键值更小,则应该往 node子树里放,继续将 newNodenode 的左子节点作为参数传入 insertNode 进行递归处理,如果左子节点不存在,则直接将新节点作为左子节点,结束递归。
  2. 如果 newNode 的键值大于等于 node 的键值,则应该往 node子树里放,将新节点和右子节点作为参数传入 insertNode 进行递归。结束递归的条件也是当某个右子节点不存在时,将新节点作为右子节点,插入完成。
// 增
insert(key) {
  const newNode = new CreateNode(key)
  this.insertNode(newNode, this.root)
}
insertNode(newNode, node) {
  // 1.判断原来根节点有没有值
  if (this.root) {
    // 有值
    if (node.key > newNode.key) {
      // 如果新节点的 key 更小,则放在左边,判断 node 的 left 是否有值
      if (node.left) {
        this.insertNode(newNode, node.left)
      } else {
        node.left = newNode
      }
    } else {
      // 新节点的 key 更大,放右边
      if (node.right) {
        this.insertNode(newNode, node.right)
      } else {
        node.right = newNode
      }
    }
  } else {
    // 没值
    this.root = newNode
  }
}

下面插入 7、4、3、5、11 和 8 这几个数:

const bst = new BinarySearchTree()
bst.insert(7)
bst.insert(4)
bst.insert(3)
bst.insert(5)
bst.insert(11)
bst.insert(8)

形成的二叉搜索树应该如下图所示:

2022-02-04_000228.png

至此,本文就先介绍关于二叉搜索树的增加节点的操作,而修改操作,其实可以给节点添加 value 属性,然后存储对应 key 值的数据,只要能通过 key 值找到该节点,自然能对 value 进行相关修改,就不再多费笔墨了。至于查询和删除节点的方法,由于需要考虑的情况较多,将放在之后的篇章继续介绍。

感谢.gif 点赞.png