二叉树查找树虽然是个好东西,但并不是人人都能掌握其全部操作的原理。相对于二叉查找树的查找、添加,删除二叉查找树的节点貌似要难了很多。一开始看二叉查找树节点删除的时候,我和大多数人一样,面对实例代码,一丢丢也看不懂,不过推荐大家在以后遇见看不懂、复杂的代码块时试着用流程图去分析其中每一步的意义,这样或许能更好的解决问题。
首先,先展示二叉查找树的实现代码
//节点类
function node(data,left,right){
this.data=data;
this.left=left;//储存左节点
this.right=right;//储存右节点
this.show=show;
}
function show(){
return this.data;
}
//二叉查找树类
function BST(){
this.root=null;
this.insert=insert;
this.inOrder=inOrder;//中序遍历
this.perOrder=perOrder;//先序遍历
this.posOrder=posOrder;//后序遍历
this.getMax=getMax;//获得值最大的节点
this.getMin=getMin;//获得值最小的节点
this.find=findData;//查找节点
this.remove=removeSurface;//删除节点
}
function insert(data){
data-=0;
var n=new node(data,null,null);
if(this.root==null){
this.root=n;
}else{
var current=this.root;
var parent;
for(;;){
parent=current;
if(data<current.data){
current=current.left;
if(current==null){
parent.left=n;
break;
}
}else{
current=current.right;
if(current==null){
parent.right=n;
break;
}
}
}
}
}
//中序遍历
function inOrder(node=this.root){
if(node!=null){
inOrder(node.left);
console.log(node.show()+" ");
inOrder(node.right);
}
}
//先序遍历
function perOrder(node=this.root){
if(node!=null){
console.log(node.show()+" ");
perOrder(node.left);
perOrder(node.right);
}
}
//后序遍历
function posOrder(node=this.root){
if(node!=null){
posOrder(node.left);
posOrder(node.right);
console.log(node.show());
}
}
function getMax(node=this.root){
if(node.right==null){
return node;
}else{
return getMax(node.right);
}
}
function getMin(node=this.root){
if(node.left==null){
return node;
}else{
return getMin(node.left);
}
}
function findData(data,current=this.root){
if(data==current.data){
return current;
}else if(data<current.data){
if(current.left==null){
return false;
}else{
current=current.left;
return findData(data,current);
}
}else if(data>current.data){
if(current.right==null){
return false;
}else{
current=current.right;
return findData(data,current);
}
}
}
function removeSurface(data){
this.root=removeNode(this.root,data);
}
function removeNode(node,data){
if(node==null){ //当树为空树时
return null;
}else if(node.data==data){ //当当前节点的值为data时
if(node.left==null&&node.right==null){ //当当前节点为叶子时
return null;
}else if(node.left==null){ //左子树为空
return node.right;
}else if(node.right==null){ //右子树为空
return node.left;
}else{
var tempNode=getMin(node.right);
node.data=tempNode.data;
node.right=removeNode(node.right,tempNode.data);
return node;
}
}else if(data<node.data){
node.left=removeNode(node.left,data);
return node;
}else{
node.right=removeNode(node.right,data);
return node;
}
}
相信除了最后删除节点的部分,其他的代码大家都可以轻而易举的看懂,那么问题又来了——特么删除节点那段代码究竟在讲神马。。。。。
根据之前遇见看不懂、复杂的代码我们就用流程图来分析其中每一步的作用,所以,我们用这个方法试一下
首先,removeSurface和removeNode两个函数分别的目的是什么?前者的目的是将二叉查找树重写,后者的目的是在以node节点(也就是当前节点)为根节点的子树中删除值为data的节点。为什么要重写二叉查找树呢?因为二叉查找树节点的分布是按照值大的在右边、值小的在左边的规律排列的,牵一发而动全身,像链表那种方式删除节点时不行的。
removeNode函数第一次调用的时候,node为整个二叉树的根节点,removeNode函数的执行流程如下:(本来想画流程图的)
1.设置当前节点为根节点
2.第一个判断node是否为空主要是针对第一次调用的时候检查二叉树是不是空树
3.当前节点值等于被删除节点值时
<1> 如果其左右节点均为空,证明其是叶子节点,那么直接return null那么之后通过当前节点的父元素就访问不到该元素了
<2> 如果只有左子树为空,return node.right将当前节点替换为当前节点的右节点,当前节点父元素的子节点就是当前元素的右节点,当前节点就被跳过(删除)了。只有右子树为空时原理也是如此。
<3> 最让人头疼的部分来了,如果左右节点都不为空怎么办…..先通过getMin函数查找以当前元素为根节点的子树中右子树最小值所对应的节点(其实也可以找左子树的最大值),将当前元素替换成右子树最小值所对应的节点,然后用removeNode递归的方式在以当前元素为根节点的子树中删除右子树最小值所对应的节点。为什么要找右子树最小值(或者左子树的最大值)呢?因为右子树最小值比左子树的所有值都大,却比右子树的所有值小,这正是根节点的特性,用它来替代根节点再适合不过了。
4.当被删除节点的值小于当前节点值时,通过node.left=removeNode(node.left,data)将当前节点设置为原当前节点的左节点,并重写以当前节点为根节点的树。当被删除节点的值大于当前节点值时,原理也是如此。
综上所述,便是我对JavaScript二叉查找树删除节点的实现原理的一些理解。如果有不清楚或者错误的地方,欢迎在下方评论区中指出,阿里嘎多。