二叉排序树又称“二叉搜索树”,是一种左子树比根结点小,右子树比根结点大的特殊二叉树的形式;因为其排序的特殊性,所以在查找某个结点的时候可以遵循某种规律,因此其查找算法相较普通的二叉树效率高的不止一点点;而实际开发中使用了二叉树这种数据结构的,大部分都是二叉排序树,因此掌握它,尤为重要!
一、认识二叉排序树
二叉排序树,需要左子树小于根结点,右子树大于根结点,很显然上面两图都不满足条件。
来看看下面这张图,没错,他就是二叉排序树:
二、初始化二叉排序树
仔细观察二叉排序树的结构,发现他本质和二叉树没什么区别,都是一个数据域和两个指针域;所以空树就可以用null来表示,注意一下这一次代码实现指针域的时候采用的是“键值对”的形式,键就是结点的唯一标识。
实现代码:
function BST() {
//初始化空树
this.tree = null;
}
function TreeNode(key, value) {
//实现键值对的数据域
this.key = key;
this.value = value;
this.left = null;
this.right = null;
}
三、构造二叉排序树
接下来进入主题:二叉排序树的左子树都比根节点小,而右子树都比根结点大,根据这个核心思路,如果是空树,直接就让根结点等于插入的这个结点就好了。举个例子再来写代码:
实现代码:
//非递归插入结点
BST.prototype.insertNode = function (key, value) {
const node = new TreeNode(key.toString(), value);
//如果二叉树为空,则直接放入根节点
if (!this.tree) {
this.tree = node;
return;
}
let p = this.tree;
while (p) {
//如果key小于根节点,搜索左子树
if (key < p.key) {
if (!p.left) {
p.left = node;
break;
} else {
p = p.left;
}
}
//如果key大于根节点,搜索右子树
if (key > p.key) {
if (!p.right) {
p.right = node;
break;
} else {
p = p.right;
}
}
}
}
当然,如果嫌弃以上代码啰嗦,就使用递归:
//递归插入结点
BST.prototype.insertNodeByRec=function(key,value){
const node = new TreeNode(key.toString(), value);
if (!this.tree) {
this.tree = node;
return;
}
this.recursion(this.tree,node);
}
BST.prototype.recursion=function(p,node){
if (p) {
//搜索左子树
if (p.key > node.key) {
if(!p.left){
p.left=node;
}else{
p.left = this.recursion(p.left, node);
}
}
if(p.key < node.key){
if(!p.right){
p.right=node;
}else{
p.right=this.recursion(p.right,node);
}
}
return p;
}
}
友情提示:插入相同结点会出现死循环,因为插入相同结点没有做出判断,导致无法退出循环,改这个bug很简单,只需判断插入相同结点的时候做出覆盖原来的结点即可,这个bug就交给你改了。哈哈
四、搜索结点
搜索结点的大致思路和插入结点是完全一致的:
//搜索结点
BST.prototype.findNode=function(key){
let p=this.tree;
key=key.toString();
while(p){
//相等即返回该结点
if(key<p.key){
p=p.left;
}else if(key>p.key){
p=p.right;
}else{
return p.value;
}
}
return null;
}
//递归搜索结点
BST.prototype.findNodeByRec=function(tree,key){
key=key.toString();
if(tree){
if(key<tree.key){
return this.findNodeByRec(tree.left,key);
}else if(key>tree.key){
return this.findNodeByRec(tree.right,key);
}else{
return tree.value;
}
}
return null;
}
五、删除结点
重点来说说删除结点,因为这是最麻烦的;
删除结点分情况:
① 删除叶子结点,这个好办,叶子结点没有子孙后代,不拖家带口的,删除起来很简单,直接将其置为null
② 删除只有一个子结点的结点,这个要麻烦点,首先找到该结点(B)的父结点(A),再找到该结点的唯一孩子结点(C),判断C结点位于B的哪一边(left或right),再判断B位于A的哪一边(left或right),最后将A的(假设是left)指向B的(假设是right),即可
③ 删除有两个子结点的结点,这个最麻烦,但是有规律,万物只要有规律,那处理起来就很简单;删除有两个子结点的结点,就需要找到key最接近该结点的那个结点来代替它即可,如何找key最相近的呢?很简单:找该结点的左子树的最右边的结点或该结点的右子树的最左边的结点。(因为现在时间挺晚的,所以图片就不画了,如果有需要的请在评论区留言,我后期将补上图片)
实现代码:
//查找结点的引用
BST.prototype._findNode=function*(key){
key=key.toString();
let q=this.tree;
let p=null;
while(q){
if(key<q.key){
p=q;
q=q.left;
}else if(key>q.key){
p=q;
q=q.right;
}else{
yield p;
yield q;
// return;
}
}
return null;
}
//删除结点
BST.prototype.deleteNode=function(key){
let p = this.tree;
let _Node=this._findNode(key);
let parentNode=_Node.next(); //获取父结点
let childNode=_Node.next(); //获取子结点
_Node.return(undefined);
if(!childNode.value){
return;
}
let num=0; //获取childNode的子结点个数,以此判断结点属性
if(childNode.value.left){
num++;
}
if(childNode.value.right){
num++;
}
//判断该结点是父结点的哪边的孩子
// let isLeft=(parentNode.value.left && parentNode.value.left.key === childNode.value.key) ? true : false;
//删除叶子结点
if(num===0){
//如果是一个根节点
if(!parentNode.value){
this.tree=null;
return;
}
let isLeft=(parentNode.value.left && parentNode.value.left.key === childNode.value.key) ? true : false;
//直接将其父节点下的该结点置null
if(isLeft){
parentNode.value.left=null;
}else{
parentNode.value.right=null;
}
}
//如果是只有一个子结点的结点
if(num===1){
//如果是根节点
if(!parentNode.value){
this.tree=childNode.value.left ? childNode.value.left : childNode.value.right;
return;
}
let isLeft=(parentNode.value.left && parentNode.value.left.key === childNode.value.key) ? true : false;
//再判断子结点位于该结点的哪边
let isChildLeft= childNode.value.left ? true : false;
isLeft ?
(isChildLeft ? parentNode.value.left = childNode.value.left : parentNode.value.left = childNode.value.right)
:
(isChildLeft ? parentNode.value.right = childNode.value.left : parentNode.value.right = childNode.value.right);
}
//删除有两个子结点的结点
if(num===2){
let $left = childNode.value.left;
let $right=childNode.value.right;
let i=0,j=0;
while($left.right || $right.left){
if($left.right){
$left=$left.right;
i++;
}
if($right.left){
$right=$right.left;
j++;
}
}
//规定谁近找谁
if(i<j){
//找左子树的最右边,即$left
//将该结点删除并替换
this.deleteNode($left.key);
childNode.value.key=$left.key;
childNode.value.value=$left.value;
}else{
//找右子树的最左边,即$right
//将该结点删除并替换
this.deleteNode($right.key);
childNode.value.key=$right.key;
childNode.value.value=$right.value;
}
}
}
六、其他方法
二叉排序树还有很多方法,比如查找最大值,最小值,销毁二叉排序树,求深度等等,其中最大值已经在本例代码中实现,求深度已经在二叉树那一篇文章中实现了,不再重复,其他方法大家自行实现。
完整源码:
'use strict'
//二叉排序树
const BST = function () {
function BST() {
//初始化空树
this.tree = null;
}
function TreeNode(key, value) {
//实现键值对的数据域
this.key = key;
this.value = value;
this.left = null;
this.right = null;
}
//非递归插入结点
BST.prototype.insertNode = function (key, value) {
const node = new TreeNode(key.toString(), value);
//如果二叉树为空,则直接放入根节点
if (!this.tree) {
this.tree = node;
return;
}
let p = this.tree;
while (p) {
//如果key小于根节点,搜索左子树
if (key < p.key) {
if (!p.left) {
p.left = node;
break;
} else {
p = p.left;
}
}
//如果key大于根节点,搜索右子树
if (key > p.key) {
if (!p.right) {
p.right = node;
break;
} else {
p = p.right;
}
}
}
}
//递归插入结点
BST.prototype.insertNodeByRec=function(key,value){
const node = new TreeNode(key.toString(), value);
if (!this.tree) {
this.tree = node;
return;
}
this.recursion(this.tree,node);
}
BST.prototype.recursion=function(p,node){
if (p) {
//搜索左子树
if (p.key > node.key) {
if(!p.left){
p.left=node;
}else{
p.left = this.recursion(p.left, node);
}
}
if(p.key < node.key){
if(!p.right){
p.right=node;
}else{
p.right=this.recursion(p.right,node);
}
}
return p;
}
}
//最大值
BST.prototype.maxNode=function(){
let p=this.tree;
if(p){
while(p.right){
p=p.right;
}
return p;
}
}
//递归最大值
BST.prototype.maxNodeByRec=function(tree){
if(!tree){
return;
}
if(tree.right){
return this.maxNodeByRec(tree.right);
}
return tree;
}
//搜索结点
BST.prototype.findNode=function(key){
let p=this.tree;
key=key.toString();
while(p){
//相等即返回该结点
if(key<p.key){
p=p.left;
}else if(key>p.key){
p=p.right;
}else{
return p.value;
}
}
return null;
}
//递归搜索结点
BST.prototype.findNodeByRec=function(tree,key){
key=key.toString();
if(tree){
if(key<tree.key){
return this.findNodeByRec(tree.left,key);
}else if(key>tree.key){
return this.findNodeByRec(tree.right,key);
}else{
return tree.value;
}
}
return null;
}
//查找结点的引用
BST.prototype._findNode=function*(key){
key=key.toString();
let q=this.tree;
let p=null;
while(q){
if(key<q.key){
p=q;
q=q.left;
}else if(key>q.key){
p=q;
q=q.right;
}else{
yield p;
yield q;
// return;
}
}
return null;
}
//删除结点
BST.prototype.deleteNode=function(key){
let p = this.tree;
let _Node=this._findNode(key);
let parentNode=_Node.next(); //获取父结点
let childNode=_Node.next(); //获取子结点
_Node.return(undefined);
if(!childNode.value){
return;
}
let num=0; //获取childNode的子结点个数,以此判断结点属性
if(childNode.value.left){
num++;
}
if(childNode.value.right){
num++;
}
//判断该结点是父结点的哪边的孩子
// let isLeft=(parentNode.value.left && parentNode.value.left.key === childNode.value.key) ? true : false;
//删除叶子结点
if(num===0){
//如果是一个根节点
if(!parentNode.value){
this.tree=null;
return;
}
let isLeft=(parentNode.value.left && parentNode.value.left.key === childNode.value.key) ? true : false;
//直接将其父节点下的该结点置null
if(isLeft){
parentNode.value.left=null;
}else{
parentNode.value.right=null;
}
}
//如果是只有一个子结点的结点
if(num===1){
//如果是根节点
if(!parentNode.value){
this.tree=childNode.value.left ? childNode.value.left : childNode.value.right;
return;
}
let isLeft=(parentNode.value.left && parentNode.value.left.key === childNode.value.key) ? true : false;
//再判断子结点位于该结点的哪边
let isChildLeft= childNode.value.left ? true : false;
isLeft ?
(isChildLeft ? parentNode.value.left = childNode.value.left : parentNode.value.left = childNode.value.right)
:
(isChildLeft ? parentNode.value.right = childNode.value.left : parentNode.value.right = childNode.value.right);
}
//删除有两个子结点的结点
if(num===2){
let $left = childNode.value.left;
let $right=childNode.value.right;
let i=0,j=0;
while($left.right || $right.left){
if($left.right){
$left=$left.right;
i++;
}
if($right.left){
$right=$right.left;
j++;
}
}
//规定谁近找谁
if(i<j){
//找左子树的最右边,即$left
//将该结点删除并替换
this.deleteNode($left.key);
childNode.value.key=$left.key;
childNode.value.value=$left.value;
}else{
//找右子树的最左边,即$right
//将该结点删除并替换
this.deleteNode($right.key);
childNode.value.key=$right.key;
childNode.value.value=$right.value;
}
}
}
return BST;
}();