- 上篇博客介绍了二叉搜索树的创建,插入结点,遍历结点,查找结点,最大结点,最小结点,后继结点。
- 那么这篇博客要重点介绍二叉搜索树的节点删除函数,一些数据结构的书籍里介绍的二叉搜索树删除节点的方法说的非常之模糊,能把一个老司机说成萌新。
二叉搜索树删除结点
只有右孩子的情况
只有左孩子的情况
没有孩子的情况
根据上面的图片显示,从而想到一个问题:在node z 只有一个孩子结点或者没有孩子结点情况,为什么孩子结点直接顶替他的位置就是删除结点的操作。
这个问题是这样解决的
- 只有右孩子情况:根据结点的排序z r,如果删除node z结点结点,从这里排列的情况来看,r就是他后面的一个结点,或者是他邻居的结点,所以r可以直接替换。
- 只有左孩子情况:同理排列r z。注意node z的右子树为空,所以到z就结束了,z为最大的结点,所以删除z,在树里r结点直接顶上去。
- 没有孩子的情况:无需排列了,z结点是光棍,啥都没了。熟话说一人吃饱全家不饿,无忧无虑,所以这种情况删除z结点最快
node z只有一个结点或者没有结点的代码执行流程是代码中的注释要仔细看
//node z是要删除的结点,赋值给y
y=z;
//x指向的是 y 的孩子结点,等同于上图的 r 结点
if(y->left != NULL)
x = y->left;
else
x = y->right;
//调整x结点指向父亲结点的位置,这个时候就相当于x顶替上去,准备把y换下。
if(x != NULL)
x->parent=y->parent;
/*
x的父亲结点已经指向了y的父亲结点
想要顶替别人还要知道那个人(node z)在他的家庭里中什么地位
这一步就是要清楚知道y是他父亲的左儿子还是右儿子,还是整颗树的根节点。
*/
if(y->parent == NULL)
tree = x;
else if(y == y->parent->left)
y->parent->left = x;
else
y->parent->right = x;
/*
x顶替y的结束了: 认了y的爸爸当爸爸,然后拥有和y一样在家庭的地位,此时y就多余了,
那么就要让y下台
*/
free(y);
结论: 只有一个或者没有孩子的node z是很好处理,只要让他的孩子顶替上去就行了
- 找到node z后继结点,后继结点的地址赋值给y,
- x作为y结点的孩子结点,然后像上面的x顶替y的方法一样,让x顶替y,
- 然后把y的值赋给z就行了,就相当于把z给删掉了。
下面有两种情况我们看一下
-
z的后继结点y,y也是z的右孩子。如下图
-
z的后继结点y,y在z的右子树中,且y不是z的右孩子。这种情况如下图
node z存在两个结点的代码执行流程是
注意:代码流程中存在一些函数,在上篇博客中,而且代码中的注释要仔细看
//node z是要删除的结点,找到他的后继结点赋值给y
y=bstree_successor(z);
//x指向的是 y 的孩子结点
if(y->left != NULL)
x = y->left;
else
x = y->right;
//调整x结点指向父亲结点的位置,这个时候就相当于x顶替上去,准备把y换下。
if(x != NULL)
x->parent=y->parent;
/*
x的父亲结点已经指向了y的父亲结点
想要顶替别人还要知道那个人(node y)在他的家庭里中什么地位
这一步就是要清楚知道y是他父亲的左儿子还是右儿子,还是整颗树的根节点。
*/
if(y->parent == NULL)
tree = x;
else if(y == y->parent->left)
y->parent->left = x;
else
y->parent->right = x;
/*
我们已经知道,y已经被顶替了。但是y被顶替了,而z逍遥自在没人惯了违背初衷了。
不要着急,只要把y的值给z就行了。
不信的话,看看上图就知道了,是不是感觉到很巧妙很神奇。↖(^ω^)↗
*/
if(y != z)
z->key = y->key;
/*
x顶替y的结束了: 认了y的爸爸当爸爸,然后拥有和y一样在家庭的地位,此时y就多余了,
那么就要让y下台
*/
free(y);
下一步就是我要把上面的两个方法合并到一起
注意: z是传入删除结点的地址
node * bstree_delete(bst_tree tree, node * z)
{
node * x = NULL;//x充当 要删除结点z的左结点或者右结点或者空结点,
node * y = NULL;//y充当 要删除结点z
/**
* 如果z结点存在左右结点,那么就查找他的后继结点,他的后继结点当作y
* 如果存在一个结点或者不存在结点,那么本身结点就当作y
*/
if((z->left == NULL) || (z->right == NULL))
y=z; //存在一个结点或者没有结点情况
else
y=bstree_successor(z); //存在两个结点情况
//x指向y的左孩子或者右孩子
if(y->left != NULL)
x = y->left;
else
x = y->right;
//调整x父亲结点的位置。使其x的父亲结点指向y的父亲结点
if(x != NULL)
x->parent = y->parent;
//调整y父节点指向x
if(y->parent == NULL)
tree = x;
else if(y == y->parent->left)
y->parent->left = x;
else
y->parent->right = x;
//y的值赋给z就相当于删除了z,这种做法很巧妙,可以结合上图查看。
if(y != z)
z->key = y->key;
free(y);
return NULL;
}
下面这一个判断代码,本来非常纠结,为什么要进行这种的验证呢。
目前的想法是:根据上面图片所展示的情况,如果y->left != NULL成立,那么假设以y为根节点(y结点开始,下面所有的孩子结点),y的value是最大的。
如果y不是最大结点,都是走下面那个代码 x=y->right
//x指向的是 y 的孩子结点
if(y->left != NULL)
x = y->left;
else
x = y->right;
全部源码地址和图片的源文件:source code and visio document
水平有限,可能存在语句不通,代码错误。please email me: chenrui@marsdl.com
参考文献 Introduction to Algorithms [MIT press]