1. 概念
二叉搜索树具有一下四点性质:
(1)所有节点关键码都互不相同
(2)左子树上所有节点的关键码都小于根节点的关键码
(3)右子树上所有节点的关键码都大于根节点的关键码
(4)左右子树也是二叉搜索树
关键码是节点所保存元素中的某个属性,它能够唯一的表示(区分)这个节点。对二叉搜索树进行中序遍历,就可以按照关键码的大小从小到大的顺序将各节点排列起来,因此,二叉搜索树也叫二叉排序树,下图是几个二叉搜索树的例子
插入时,从根节点开始,被插入元素的关键码如果比根节点关键码小,则进入到左子树中执行插入操作,如果左子树不存在,则被插入元素成为左孩子;反之,进入到右子树中执行插入操作,如果右子树不存在,则被插入元素成为右孩子,如果被插入元素的关键码已经存在,则返回false。
按照 19, 27, 40, 35, 25, 10, 5, 17, 13, 7, 8 的顺序插入到二叉搜索树中,下图演示了这个过程。
var insert_data = function(node, data){
if(root == null){
root = new TreeNode(data);
return true;
}
if(data < node.data){
if(node.leftChild){
// 往左子树里插入
return insert_data(node.leftChild, data);
}else{
// 创建节点并插入
var new_node = new TreeNode(data);
node.leftChild = new_node;
new_node.parent = node;
return true;
}
}else if(data > node.data){
if(node.rightChild){
// 向右子树里插入
return insert_data(node.rightChild, data);
}else{
// 创建节点并插入
var new_node = new TreeNode(data);
node.rightChild = new_node;
new_node.parent = node;
return true;
}
}else{
// 如果相等,说明已经存在,不能再插入
return false;
}
};
this.insert = function(data){
return insert_data(root, data);
};
2.3 搜索
与插入算法非常接近,仍然是从树的根节点开始,如果被搜索元素的关键码比根节点关键码小,则进入到左子树中进行搜索,若左子树不存在,返回null,如果被搜索元素的关键码比根节点关键码大,则进入到右子树中进行搜索,若右子树不存在,返回null,如果根节点的关键码和被搜索元素的关键码相同,返回这个根节点。
var search_data = function(node, data){
if(node == null){
return null;
}
if(data == node.data){
return node;
}else if(data < node.data){
return search_data(node.leftChild, data);
}else{
return search_data(node.rightChild, data);
}
};
this.search = function(data){
return search_data(root, data);
};
2.4 删除
删除一个节点时,要考虑到必须将被删除节点的子孙节点连接到树上,同时保证二叉搜索树的性质。
根据被删除节点的左右子孩子,可以总结一下几种情况:
- 被删除节点左右孩子都不存在
- 被删除节点没有右孩子
- 被删除节点没有左孩子
- 被删除节点左右孩子都存在
对于第1种情况,最为简单,只需要让其父节点指向它的指针指向null即可
对于第2种情况,用左孩子替代它的位置
对于第3种情况,用右孩子替代他的位置
对于第4中情况,稍稍有些复杂,首先,去被删除节点的右子树中找到中序遍历下的第一个节点,假设节点的data数据为x,将被删除节点的data替换成x,而后,在被删除节点的右子树中执行删除x的操作
下图演示了删除3个节点的过程,分别对应了第 2 3 4 种情况
在删除27时,它左右子孩子都存在,就去右子树中找中序遍历下的第一个节点,这个节点是35,于是将27替换成35,然后在右子树中执行删除35的操作,而在右子树中,35没有左孩子,有右孩子,问题演变成了第3种删除情况。
示例代码
var link_parent = function(parent, node, next_node){
// 连接父节点和子节点
if(parent==null){
root = next_node;
root.parent = null;
}else{
if(parent.leftChild && parent.leftChild.data == node.data){
parent.leftChild = next_node;
}else{
parent.rightChild = next_node;
}
}
};
var remove_data = function(node, data){
if(node==null){
return false;
}
if(data < node.data){
// 去左子树里删除
return remove_data(node.leftChild, data);
}else if(data > node.data){
// 去又子树里删除
return remove_data(node.rightChild, data);
}else{
if(node.leftChild && node.rightChild){
// 左右两个子树都存在,那么,找到中序下的第一个节点,这个节点在右子树里最小
var tmp = node.rightChild;
while(tmp.leftChild){
tmp = tmp.leftChild;
}
// 被删除点的值等于中序下第一个节点的值
node.data = tmp.data;
// 去右子树里删除中序下的第一个节点
return remove_data(node.rightChild, tmp.data);
}else{
var parent = node.parent; // 找到父节点
if(!node.leftChild){
// 没有左孩子
link_parent(parent, node, node.rightChild);
}else{
link_parent(parent, node, node.leftChild);
}
return true
}
}
};
this.remove = function(data){
return remove_data(root, data);
};