在前端的工做当中,二叉搜索树不怎么常见,虽然没有快排、冒泡、去重、二分、希尔等算法常见,可是它的做用,在某些特定的场景下,是很是重要的。javascript
目前es6的使用场景比较多,因此我准备能用es6的地方就用es6去实现。
复制代码
上图是我从网上找的,最主要是让你们看一下,树长啥样。前端
在这里简单的介绍一下有关二叉搜索树的术语,在后续讨论中将会提到。一棵树最上面的节点称为 根节点,若是一个节点下面链接两个节点,那么该节点称为父节点,它下面的节点称为子 节点。一个节点最多只有 0 - 2 个子节点。没有任何子节点的节点称为叶子节点。vue
二叉搜索树就是一种非线性的数据结构,通常是用来存储具备层级关系的数据,好比,咱们要作一个可视化的文件系统,相似于云盘网页版,它有区分不一样的文件夹,每一个文件夹下面都有不一样的内容,它们每一个文件夹,是没有任何的关系的,惟一的关系,就是同一层级的文件夹,都有一个父级文件夹。java
和非线性数据结构相反的,是线性数据结构,线性数据结构其实在平时就比较常见了,前端经常使用的线性数据结构有:栈、队列、数组。node
选择二叉搜索树而不是那些基本的数据结构,是由于在二叉搜索树上进行查找很是快,为二叉搜索树添加或删除元素 也很是快。es6
二叉搜索树的实现功能包括添加节点、删除节点、查询节点(最大值、最小值,某一个指定值);算法
二叉搜索树的遍历方式包括中序遍历、先序遍历、后序遍历;数组
在建立一个节点时,咱们要记住二叉搜索树节点的特性,子节点不容许超过两个:数据结构
class Node {
constructor({data = null, left = null, right = null}){
this._data = data
this._left = left
this._right = right
}
show(){
return this._data
}
}
复制代码
这样,建立了一个节点,默认为 null,有 left 和 right 两个节点,添加一个 show 方法,显示当前节点的数据;post
如今,咱们有了一个节点,知道了这个节点有哪些属性,接下来,咱们建立一个能够操做这些节点的类:
class BinarySearchTree {
constructor(){
this._root = null
}
}
复制代码
这里咱们建立了一个根节点,这个根节点就是二叉搜索树最底层的那个根,接下来,咱们先添加一个添加节点的方法。
class BinarySearchTree {
constructor() {
this._root = null
}
insert(data) {
let node = new Node({
data: data
})
if (this._root == null) {
this._root = node
}
else {
let current, parent
current = this._root
while (true) {
parent = current
if (data < current.data) {
current = current.left
if (current == null) {
parent.left = node
break
}
}
else {
current = current.right
if (current == null) {
parent.right = node
break
}
}
}
}
}
}
复制代码
这里,咱们添加了一个 insert 方法,这个方法就是用来添加节点的;
初始化先检查根节点 root 是不是 null,若是是 null,那说明不存在根节点,以当前添加的节点为根节点;
若是存在根节点的话,那么接下来值的对比目标,初始化以根节点作对比目标,确认大于根节点,仍是小于根节点;
若是小于的话,那么就下次用根节点的左节点 left 来作对比,固然,若是左节点是空的,直接把当前要添加的节点看成左节点 left 就ok了。
若是大于的话,和小于反之;
而后咱们如今,开始添加值,测试一些方法:
let bst = new BinarySearchTree()
bst.insert(2)
bst.insert(1)
bst.insert(3)
复制代码
咱们添加了一个根节点,是2,而后添加了两个叶子的节点 1 和 3 ,1 就在根节点 2 的左侧, 3 就在根节点 2 的右侧,接下来咱们加一个删除节点的方法。
class BinarySearchTree {
constructor() {
this._root = null
}
remove(data) {
this._root = this._removeNode(this._root, data)
}
_removeNode(node, data) {
if (node == null) {
return null
}
if (data == node._data) {
if (node._left == null && node._right == null) {
return null
}
if (node._left == null) {
return node._right
}
if (node._right == null) {
return node._left
}
var tempNode = this._getSmallest(node._right)
node._data = tempNode._data
node._right = this._removeNode(node._right, tempNode._data)
return node
} else if (data < node._data) {
node._left = this._removeNode(node._left, data)
return node
} else {
node._right = this._removeNode(node._right, data)
return node
}
}
_getSmallest(node) {
if (node._left == null) {
return node
} else {
return this._getSmallest(node._left)
}
}
}
复制代码
这个是用来删除某一个节点的方法。
这个方法最后返回的是一个新的 node 节点,若是第一次调用的时候 node 是 null 的话,说明这个二叉搜索树是空的,不存在任何值,直接返回空;
若是当前要删除的值,是当前节点的值,那么去检查它的左右节点是否存在值,若是都不存在,直接返回 null,由于说明当前的节点下面没有子级节点,就不须要对子级节点作处理了,直接删除当前节点就好;
若是左节点是 null 的,那么直接用当前节点的右节点替换当前节点;
反之,右节点是 null 的,那么直接用当前节点的左节点替换当前节点;
若是,当前要删除的节点,左右节点都存在的话,那么就去遍历要删除节点的右侧节点,若是右侧节点不存在它的左节点,那么直接返回右侧节点,若是存在,一直递归,找到最底层的一个左侧节点返回结果;
这里其实简要归纳一下,就是去找删除节点右节点树当中的最小值,来代替当前被删除节点的位置
复制代码
若是当前要删除的值,比当前节点的值小,就递归调用,一直找到当前值并删除为止;
若是当前要删除的值,比当前节点的值大,与上面反之;
接下来,咱们添加一个查找的方法:
class BinarySearchTree {
constructor() {
this._root = null
}
find(data) {
let current = this._root
while (current != null) {
if (current._data == data) {
return true
} else if (data < current._data) {
current = current._left
} else {
current = current._right
}
}
return false
}
}
复制代码
这个方法是用来查找一个值的,若是当前查询的值小于当前节点的值,那就从当前的节点左侧去查询;
反之,若是大于,就直接去右侧查询;
若是这个值始终查询不到,那么就返回 false,不然就是 true。
class BinarySearchTree {
constructor() {
this._root = null
}
getMin() {
let current = this._root
while (current._left != null) {
current = current._left
}
return current._data
}
}
复制代码
这是一个查询最小值的方法,因为二叉搜索树数据结构的特殊性,左侧的值永远是最小的,因此一直查询到低,找到最底层的左节点返回就能够了。
class BinarySearchTree {
constructor() {
this._root = null
}
getMax() {
let current = this._root
while (current._right != null) {
current = current._right
}
return current._data
}
}
复制代码
和查询最小值相反。
操做二叉搜索树节点的方式都有了,接下来该遍历二叉搜索树了。
把二叉搜索树的数据遍历一遍,用中序、先序、后序遍历,代码量比较少,代码也不是伪代码都是我本身测过的,结果就不截图了,把执行顺序的流程图发一下,结果你们感兴趣本身跑一下就行了:
class BinarySearchTree {
constructor() {
this._root = null
}
inOrder(node) {
if (node != null) {
this.inOrder(node._left)
console.log(node.show())
this.inOrder(node._right)
}
}
}
复制代码
先遍历左节点,在遍历根节点,最后遍历右节点,步骤以下:
class BinarySearchTree {
constructor() {
this._root = null
}
preOrder(node) {
if (node != null) {
console.log(node.show())
this.inOrder(node._left)
this.inOrder(node._right)
}
}
}
复制代码
这是先序遍历,先遍历根节点,在遍历左节点,最后遍历右节点,步骤以下:
class BinarySearchTree {
constructor() {
this._root = null
}
postOrder(node) {
if (node != null) {
this.inOrder(node._left)
this.inOrder(node._right)
console.log(node.show())
}
}
}
复制代码
这是后序遍历,先遍历叶子节点,从左到右,最后遍历根节点,步骤以下:
到这里,二叉搜索树讲解的就差很少了,你们对于哪里有疑问,随时欢迎评论。
原本这一章是应该写 vue 源码解析(实例化前)的最终章的,可是涉及到的东西比较多,并且我想把前几章的总结一下写到最后一章。
因此这一章就先写一章有关算法的文章,谢谢你们支持🙏