前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >怒肝 JavaScript 数据结构 — 树与二叉树

怒肝 JavaScript 数据结构 — 树与二叉树

作者头像
杨成功
发布2022-09-22 14:20:11
2930
发布2022-09-22 14:20:11
举报
文章被收录于专栏:前端砍柴人前端砍柴人

大家好,我是杨成功。

到本篇为止我们已经学习了大多数的顺序数据结构,而第一个非顺序数据结构是散列表。本篇我们学习第二种非顺序数据结构 —— ,是一个相对复杂的数据结构。

生活中提到 “”,我们肯定会想到去公园遛弯时看到的树木。一棵树只有一个主干,但是主干上面会分出无数的树枝,树枝又各自分叉产生新的树枝,这样层层分叉最终变成了我们看到那棵枝繁叶茂的大树。

其实数据结构中的树也是一样,最顶部只有一个元素,然后一个元素下包含多个子元素,子元素又包含子元素,层层包含下去,最终组成了一个庞大的数据体。

生活中最常见的树的例子,就是公司的组织架构,如图:

总裁是最高位置,下面划分了多个副总的岗位,副总下又划分经理,层层划分,形成了树状结构。现在你明白数据结构中的“树”是什么了吧?

树的相关术语

树的每个元素被称为节点,一个树结构包含了一系列父子关系的节点。最顶层的那个节点被称为根节点,其他节点全部是它的子节点。

如图,节点分为内部节点外部节点。只要有子节点的就是内部节点,最外层的没有子节点的节点,就是外部节点,也叫叶节点。

一个节点的层级关系总体上分为三种:

  • 父节点
  • 兄弟节点
  • 子节点

比如图中的节点 5,父节点是 7,兄弟节点是 9、13,子节点是 3、6。这些听着是不是很耳熟?没错,我们前端经常打交道的 DOM 节点,就是典型的树结构。

除此之外,树还有两个重要的属性:

  • 高度:从 0 开始,一共有多少层
  • 深度:某个节点处在第几层

如图中,根节点是 0 层,最下面的是第 3 层,所以树的高度为 3。这个好比数组的索引,第一个元素是 0,最后一个是 n-1,所以树的高度是 n - 1

深度的话,就相当于数组中某个元素的索引了,在第几层深度就是几。

好了,这里介绍了树的基本概念,接下来介绍二叉树。

二叉树与二叉搜索树

首先,二叉树是树的一种,拥有树的基本属性。但它的特点是每个节点最多只能有 两个 子节点:一个左侧子节点和一个右侧子节点。

二叉搜索树(BST)是二叉树的一种,但它要求必须在左侧子节点存储比父节点小的值,在右侧子节点存储比父节点大的值。上图中灰色部分由 13、12、14 三个节点组成的树就是一棵二叉搜索树。

本篇我们实现一棵二叉搜索树。

初始化类

在开始写二叉搜索树之前,需要先定义一个节点类,存储我们基本节点的数据。

代码语言:javascript
复制
class Node {
  constructor(key) {
    this.key = key
    this.left = null
    this.right = null
  }
}

看这个节点类,你会发现和之前我们学到的双向链表的节点非常相似。也是有两个属性分别表示前一个元素和后一个元素。

区别是什么呢?其实只是含义上的区别。双向链表的两个属性指向的都是兄弟元素,而上述的节点类的 leftright 指向的是子元素。

在树当中,节点也被称为

代码语言:javascript
复制
class BinarySearchTree {
  constructor(compareFn) {
    let defFn = (a, b)=> a !== b ? a > b ? 1 : -1 : 0
    this.root = null;
    this.compareFn = compareFn || defFn
  }
}

上面我们定义了一个 BinarySearchTree 类,这个类和链表的结构也差不多,其中属性 root 表示根节点,传入一个自定义函数用来自定义节点如何比较。

在这个基础类下,我们还要自定义几个方法:

  • insert(key):插入一个键
  • search(key):在树中查找一个键
  • inOrderTraverse():通过中序遍历方式遍历所有节点
  • preOrderTraverse():通过先序遍历方式遍历所有节点
  • postOrderTraverse():通过后序遍历方式遍历所有节点
  • min():返回树中最小的值
  • max():返回树中最小的值
  • remove():从树中移除某个键

本篇我们只介绍一个 insert 方法。

insert 方法

insert 方法的作用是向二叉搜索树中插入一个 key(节点)。本篇的 insert 方法要比前几篇实现的复杂一些,因为会用到很多递归。这也是为什么我们在学习树之前先要介绍递归。

如果你还不清楚递归,请参考这篇:怒肝 JavaScript 数据结构 — 递归篇

向树中插入节点有两个步骤,我们先实现第一步:

代码语言:javascript
复制
insert(key) {
  if(this.root == null) {
    this.root = new Node(key)
    return true
  } else {
    return this.insertNode(this.root, key)
  }
}

第一步是先判断当前实例是否有根节点,没有的话创建,有的话就要走在某个节点下创建子节点的逻辑。

接着我们看第二步,如何递归创建子节点。

代码语言:javascript
复制
insertNode(node, key) {
  let compare = this.compareFN(key, node.key)
  if(compare < 0) {
    if(node.left == null) {
      node.left = new Node(key)
      return true
    } else {
      this.insertNode(node.left, key)
    }
  }
  if(compare > 0) {
    if(node.right == null) {
      node.right = new Node(key)
      return true
    } else {
      this.insertNode(node.right, key)
    }
  }
  return false
}

这个代码逻辑是,首先比较节点的 key 与参数 key 谁大谁小,然后判断要填充左侧节点还是右侧节点。

填充两侧节点的逻辑是一样的,先判断节点对应的属性(leftright)是否存在,如果不存在则执行正常的添加逻辑,如果存在就获取节点,进入递归循环。

写完这个,我们来测试一下结果:

代码语言:javascript
复制
var bin_tree = new BinarySearchTree();
bin_tree.insert(20)

上面这段代码是初始化,并第一次添加,将根节点设置为了 20

然后再进行一波添加:

代码语言:javascript
复制
bin_tree.insert(17)
bin_tree.insert(15)
bin_tree.insert(24)
bin_tree.insert(33)
bin_tree.insert(19)
bin_tree.insert(36)

最终的打印结果如下:

看打印结果,符合我们上面二叉搜索树的概念。

总结

本篇我们认识了什么是树,什么是二叉树以及二叉搜索树,并创建了一个二叉搜索树的类,实现了添加节点的方法,可以说本篇是树的一个基础篇介绍。

下篇我们整体介绍树的遍历与检索,实现从树中找到我们想要的值。

本文来源公众号:程序员成功。这是学习 JavaScript 数据结构与算法的第 22 篇,本系列会连续更新一个月。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-04-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员成功 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 树的相关术语
  • 二叉树与二叉搜索树
    • 初始化类
    • insert 方法
    • 总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档