二叉搜索树BST的实现 删除结点操作
昨天晚上有点困了,就没有写BST的删除结点操作
下面来考虑BST的删除:
删除的难点就在删除一个结点之后,要保留BST的特点,删完了结点,依然是一棵BST
以下面这棵树为例:
删除出度为0的结点
如果要删除结点13:
13为叶子结点,它的出度为零,左右子树都为空,直接free掉这个结点的空间,并将其父结点的左子树令为空就好
删除出度为1的结点
如果要删除结点20:
20是中间结点,它的出度为1,有一个子结点,现在考虑的就是当20不在了之后,怎么将它的子结点19连上树
子结点19和20的父结点18满足什么样的关系呢?
由于以20为根的子树是结点18的右子树,所以满足以20为根的子树上所有的结点的值都大于18,即在删除了20以后,不管以19为根的子树是20的左子树还是右子树,由于它的所有结点以20为根的子树结点的子集,所以以19为根的子树上的所有结点的值也是满足大于18的,直接将19连上18作为18的新的右子树就好了:
删除出度为2的结点
如果要删除结点5:
受上面删除结点20的影响,你或许会想,由于5的左右子树也都分别满足子树上的所有结点的值都小于或者大于结点5,直接作为新的左右子树连上结点5的父结点10
那么要怎么操作呢?
是不是要先删除以5为根结点的整棵子树然后再插入结点4,7,6呢?
可以这样模拟试一下,就会发现,位置5的结点并没有被删除,只是将5用另外一个结点node的值代替了,然后再将node结点删除,这样既保持了二叉树的结构,又成功删除了5这个值
那么要怎样选取node的值呢?
结点node的值要满足放上5这个位置之后,它的左子树的值都小于它,右子树的值都大于它,并且node的值要小于5
考虑一个情景,有有序数列{1,2,3,4,5,6,7},删除5,需要取一个值放在5的位置上,让序列依旧有序,当然是在子序列{1,2,3,4}和{6,7}中分别找最大或最小
在这里也是一样,我们只需要选择一棵子树,比如左子树,在左子树上找一个最大值去放在5的位置,左子树的最大值当然是在它的右子树里面找了,不断的遍历右子树,直到找到一个结点node,它的rightsubtree为NULL,那么node -> val就可以用来替代删除结点的值,然后我们只需要删掉这个node,要删除的结点的值就没有啦
在这里找到的就是结点4,将4结点删除了,再将4赋值给要删除的值的结点
然后我们就发现了,替代的结点node一定是没有右子树的,也就是它的出度<2,删除node就转换成了出度为0或者1的情况
传入参数parent是要删除结点的父结点,正如上面三种情况所示,每一次的删除都会有需删除结点的父结点的指针与需删除结点的子结点的操作
//删除出度为0的结点
void Delete_0(BSTtree* parent, int val) {
//如果要删除的是根结点 根结点的父结点是自己
if (parent -> val == val) {
free(parent);
printf("最后一个结点已被删除\n");
}
else if (val > parent -> val) {
free(parent -> rightsubtree);
parent -> rightsubtree = NULL;
}
else {
free(parent -> leftsubtree);
parent -> leftsubtree = NULL;
}
}
出度为1的情况为什么会有返回值呢?
通过这些代码都可以看出来,树的每次操作都是以根结点开始的
如果这颗树退化成了类似链表的情况,需要删去树的根结点,就需要以根结点的子结点来作为新的根结点更新原来的根结点,然后再删去原根结点,避免出现之后找不到根结点无法找到这颗树的问题
//删除出度为1的结点
BSTtree* Delete_1(BSTtree* parent, int val) {
//当删除的结点是根结点的时候
if (parent -> val == val) {
BSTtree* root = parent -> rightsubtree != NULL ? parent -> rightsubtree : parent -> leftsubtree;
free(parent);
return root;
}
if (val > parent -> val) {
BSTtree* current = parent -> rightsubtree;
BSTtree* child = current -> rightsubtree != NULL ? current -> rightsubtree : current -> leftsubtree;
free(parent -> rightsubtree);
parent -> rightsubtree = child;
}
else {
BSTtree* current = parent -> leftsubtree;
BSTtree* child = current -> rightsubtree != NULL ? current -> rightsubtree : current -> leftsubtree;
free(parent -> leftsubtree);
parent -> leftsubtree = child;
}
return NULL;
}
find函数是查找这棵树里面有没有一个值,删除的时候同样要用到这个函数,如果找不到这个值,那就没有可以删除的元素
judge_child函数是用来判断要删除的结点的出度以及找到要删除结点的位置并返回,pair类型的第一个参数就是判断出度,第二的类型就是返回要删除值在哪个位置,从而可以分成不同的情况操作,
typedef pair<int, BSTtree*> P;//first是删除结点的出度,第二个是删除结点的位置
P judge_child(BSTtree* root, int val) {
BSTtree* current = root;
while (current != NULL) {
if (val == current -> val) {
if (current -> leftsubtree != NULL && current -> rightsubtree != NULL) return make_pair(2, current);
if (current -> leftsubtree != NULL || current -> rightsubtree != NULL) return make_pair(1, current);
if (current -> leftsubtree == NULL && current -> rightsubtree == NULL) return make_pair(0, current);
}
else if (val > current -> val) {
current = current -> rightsubtree;
}
else {
current = current -> leftsubtree;
}
}
current = NULL; //如果并没有这样的结点,就令第二个参数为NULL
return make_pair(-1, current);
}
bool find(BSTtree* root, int val) {
P p;
p = judge_child(root, val);
return p.first != -1 ? true : false;
}
Delete函数就是对结点的删除操作了
//有可能并没有这个元素
void Delete(BSTtree* root, int val) {
P p = judge_child(root, val);
int childs = p.first; //要删除结点的出度
int newchilds = childs; //childs != 2的newchilds就等于childs不会变
/*当出度为2的时候,之后找到替代的结点之后,要删除的结点就更换成替代的结点,相应的,出度也会有变化,调用的函数就会不同*/
if (childs == -1) {
printf("错误!树中没有该元素\n");
return;
}
//cout << "childs: " << childs << endl;
// 当只有一个子结点或者自己就是叶子结点的时候,处理很简单
if (childs == 2) {
//接下来的情况就是有两个子结点的情况
//我们先要找一个结点能够替代当前要删除的结点node,即要满足在替换掉之后,BST的特点仍能够满足
//有两种方案,在左子树找到一个值最大的结点或者在右子树找到一个最小的结点
BSTtree* replace = p.second -> leftsubtree; //由于node有两个子结点,所以只用考虑在左子树leftsubtree当中找到一个最大的值
//由于leftsubtree也是一棵BST,要找最大的结点需要在其右子树当中找
//一直沿着树干往下,直到某个结点没有右子树
while (replace -> rightsubtree != NULL) {
replace = replace -> rightsubtree;
}
//将这个找到的替代node的值删掉,记住它的值,之后赋值给node
val = replace -> val;
//cout << "replace: " << val << endl;
newchilds = judge_child(root, val).first; //此时要删除的结点就是这个作为替代的结点了
}
BSTtree* parent = root; //根结点自己就是自己的父结点
if (parent -> val != val) { //不是根结点
while (1) { //每次分别跟当前结点的左右子结点判断是否相等
//这里的操作是寻找需删除结点的父结点
if (parent -> leftsubtree != NULL) {
if (val == parent -> leftsubtree -> val)
break;
}
if (parent -> rightsubtree != NULL) {
if (val == parent -> rightsubtree -> val)
break;
}
if (val > parent -> val) {
parent = parent -> rightsubtree;
}
else if (val < parent -> val) {
parent = parent -> leftsubtree;
}
}
BSTtree* newtree = NULL;
//cout << parent -> val << endl;
if (newchilds == 0) Delete_0(parent, val);
else if (newchilds == 1) newtree = Delete_1(parent, val);
//当出度为1且删除根结点的时候需要更新根结点
if (newtree) {
root = newtree;
}
}
//覆盖要删除的值
if (childs == 2) {
p.second -> val = val;
}
}