摘要
如果说一个数据结构的出现一定是因为它能解决某些问题,我们传统的线性表有数组和链表,前者在改查的优势更大,后者在增删的优势更大。
但是往往在应用中这些功能我们是都需要用到的,在对数组或者链表的选择上就有了一些决定性。
而二叉搜索树是具备了数组改查快和链表增删快的特点而生的一种数据结构。
二叉搜索树具有二叉树的特点,并且,每个节点的左子树中所有节点都是比当前节点的值小的,每个节点的右子树所有节点都是比当前节点的值大的。
正是这种特点,使它在数据操作和检索操作上具有很高的效率。
OK,这一篇我们主要用JS来实现一个二叉搜索树。
准备工作就是定义一个二叉树的类和一个节点类。
function BST () {
this.root = null;
}
function Node (val, left, right) {
this.val = val;
this.left = left;
this.right = right;
}
1.增
要实现添加节点这个操作,我们来分为几种情况来做:
1.二叉树为空
在这种情况下,root === null,我们可以直接把新节点的值给root可以了。
2.二叉树只有一个root节点
这种情况下我们也很好判断,我们只需要判断node节点和root节点的值哪个大就可以了。
node.val > root.val : root.right = node
node.val < root.val : root.left = node
3.二叉树的节点数大于2
这种情况就比较复杂了,也是添加节点的难点所在。
我们可以这样来想这个问题,我们拿root根节点作为当前节点。
比较当前节点empty和新节点node的值的大小
node.val > empty.val : empty = empty.right
node.val < empty.val : empty = empty.left
直到当前节点一个子节点都没有了,再看新节点是放在当前节点的左边还是右边。
如图所示,如果想向二叉树中插入2这个节点,那么就是上述的步骤。
BST.prototype.add = function (val) {
let node = new Node(val, null, null);
//情况1
if (this.root === null) {
return this.root = node
}
let empty = this.root;
//循环直到empty的左子节点和右子节点都为null
while (empty.right != null || empty.left != null) {
if (val > empty.val) {
//情况2
if (empty.right == null) {
empty.right = node;
return;
} else {
empty = empty.right;
where = 'right'
}
} else {
//情况2
if (empty.left == null) {
empty.left = node;
return;
} else {
where = 'left'
}
empty = empty.left;
}
}
//判断最后新节点为empty的左子节点还是右子节点
if (val > empty.val) {
empty.right = node;
} else {
empty.left = node
}
}
2.查
第二个我们来说一下如何查找一个元素是否存在,为什么不先说删除呢?因为删除节点的前提就是要查找节点,所以我们先来说一下查找的方法。
例如我们要查找12这个节点node,其实我们想起来也是比较简单的。
我们只需要先和根节点比较,发现12 > 5
然后再和根节点的右节点比较,发现 12 > 8
然后再和8的右节点比较,发现12 === 12
OK,这就说明有12这个节点,我们就可以返回true了。
这里面你会发现我们每一步的解决方法是一样的,所以我们采用递归的思路来解决。
BST.prototype.search = function (val) {
const searchNode = function (node, val) {
//两种情况下的递归终止条件,顺序不能错
if (node === null) {
return false
}
if (node.val === val) {
return true
}
if (val > node.val) {
//右子树递归
return searchNode(node.right, val);
} else {
//左子树递归
return searchNode(node.left, val);
}
}
return searchNode(this.root, val)
}
3.删
我们已经知道怎么查找到一个节点了,现在想做的就是把这个节点删除掉,这里面我们也会分为几种情况
1. node.left === null && node.right === null
这种情况下就是删除的节点node没有子节点,那么我们就直接把这个节点删除就好了。
2. node.left === null || node.right === null
这种情况的意思就是,删除的节点node只有一个子节点,另一个子节点为null,其实这种情况也很简单,我们只需要把node的子节点对node自己进行替换就好了。
3. node.left != null && node.right != null
这种情况下就比较复杂了,删除的节点它左子节点和右子节点都有,这种情况下我们删除后还要保证二叉搜索树的特性不被更改。
例如我们删除4这个节点,现在我们不考虑算法,我们怎么能够重构二叉树花费的代价比较小,又能保证二叉树还是具有二叉搜说树的性质呢。
似乎我们可以看出来两种情况:
你会发现这两种情况的区别在于,其实我们只是找到要删除节点中,左子树最小的或者右子树最大的那个节点,把这个节点的值赋给要删除的节点,然后再放逐自己,就可以了。
所以我们可以挑一种方法,不管是左子树最小的,还是右子树最大的,都可以实现删除的方法。
BST.prototype.remove = function (val) {
//找到要删除的节点
const search = function (node, val) {
if (val === node.left.val || val === node.right.val) {
let parent = node.left.val === val ? node.left : node.right;
let where = node.left.val === val ? 'left' : 'right';
return [node, parent, where]
}
if (val > node.val) {
return search(node.right, val)
}
if (val < node.val) {
return search(node.left, val)
}
}
//保存要删除节点,要删除节点的父节点,已经删除的左右方向
let [node, parent, where] = search(this.root, val)
if (node) {
if (parent.left == null && parent.right == null) {
node[where] = null
} else if (parent.left == null) {
node[where] = parent.right;
} else if (parent.right == null) {
node[where] = parent.left;
} else {
let empty = parent
//找到左子树中最小的节点
while (empty.left.left != null) {
empty = empty.left;
}
parent.val = empty.left.val;
empty.left = null;
}
}
}
实现方法可能会有很多,看自己的代码习惯吧^ - ^
4.改
修改的话有了上面的讲解也很简单了,只需要通过查找确定当前节点,然后对值进行替换就可以了。
这里也不多说了:
BST.prototype.replace = function (oldVal, newVal) {
let node = this.root;
const replaceNode = function (node) {
if (node === null) {
return false
}
if (node.val === oldVal) {
return node.val = newVal;
}
if (node.val < oldVal) {
replaceNode(node.right, oldVal);
} else {
replaceNode(node.left, oldVal)
}
}
replaceNode(node);
}
关于二叉搜索树的增删改查这里也就说完了^ _ ^