数据结构基础-二叉排序树的删除操作

一棵搜索二叉树的结点的度有以下三种情况:0 1 2

那我们就需要依据节点不同的度进行不同的讨论

假设被删除的节点是node,我们仍然需要考虑该node是否为根节点,因为根节点无双亲结点,其索引只有一个root指针

情况一:node没有孩子,是叶子结点,度为0,直接free该节点的分配空间

(1) node不是根节点,直接把node删除,把node父亲对应的孩子指针置空

(2) node是根节点,直接删除,把根指针置空

情况二:node只有一个孩子节点,节点度为1

(1) node不是根节点,并且node只有一个孩子,就让这个孩子节点顶替node的位置,也就是node的parent的指向node的指针,要去指向该孩子

(2) node是根节点,让根指针直接指向node的唯一孩子即可,当然了先需要移动root指针,另外将原根节点free

情况三:node有两个孩子,节点度为2

我们从排序的中序序列来观察一下

假如该序列的顺序排序为:1 3 4 6 7 8 10 13 14,且该树的根节点为8,如果删除节点8的话,新树的排序为:

1 3 4 6 7 10 13 14

那么要么我们将节点7移到8的位置

或者10代替8向前移

都可以解决该问题,7是左子树中最靠右的节点,10是右子树中的根节点,是原节点8的左膀右臂

法1:让node节点的左子树中的最大值x来替换node节点的值,转化为删除x所在节点:如果x是叶子结点转化为情况一,如果x不是叶子结点转化为情况二(x只有左孩子) x是node左子树最靠右的节点

法2:让node节点的左子树中的最小值x来替换node节点的值,转化为删除x所在节点:如果x是叶子结点转化为情况一,如果x不是叶子结点转化为情况二(x只有右孩子) x是node右子树最靠左的节点

所以综上所述

①.对于叶子结点的删除,保存其父亲节点位置后,直接删除该叶子结点,令其父亲节点指向它的指针指向NULL;

②.对于度为1的节点删除,保存该节点的父亲节点位置后,另其父亲节点指向该节点的指针,指向其唯一的左/右孩子,然后删除该节点即可;

(针对以上两个类型的节点,我们都需要判断其是否为根节点,如果是根节点就要做根指针的替换,如指向NULL或者换成其对应的左/右孩子)

③.对于度为2的节点删除,通过分析我们知道了,我们需要找的是度为2的节点,在中序序列中的,前驱和后继,用以替换其位置,因为其本身具有特殊性,不能像度为0或者1的节点一样直接进行节点的删除和双亲结点指针的变换,以满足新的二叉搜索树仍然保持一个顺序序列。

所以我们通过寻找其前驱后继结点进行替换的方法,先进行数据上的整体替换,达到顺序序列的要求,然后再对被替换的(此时被替换结点成为了多余节点,即需要删除节点)进行删除操作,仍然是分析该节点的情况,其度为0或1,然后对应节点度进行情况一、二的结点的修正,完成一个节点的删除。

下面是代码操作:

//删除值为k的节点
void delete(int key){
    Node *node = root;
    Node *pre = NULL;
    if(node!=NULL){
        while(node->data != key){
            pre = node;
            if(key < node->data){
                node = node->left;
            }
            else if(key > node->data){
                node = node->right;
            }
        }
        if(node->left == NULL && node->right == NULL){//情况一,度为0的节点
            if(pre==NULL){//node的父亲为空,那么node为根节点
                root = NULL;
                free(node);
                node = NULL;//野指针
            }
            else{
                if(pre->left == node){
                    pre->left = NULL;
                    free(node);
                    node == NULL;
                }
                else{
                    pre->right == NULL;
                    free(node);
                    node = NULL;
                }
            }
        }
        
        //情况二-1
        else if(node->left != NULL && node->right == NULL){
            if(pre == NULL){
                root = node->left;
                node->left = NULL;
                free(node);
                node = NULL;
            }
            else{
                if(node == pre->left){
                    pre->left = node->left;
                    node->left = NULL;
                    free(node);
                    node = NULL;
                }
                else{
                    pre->right = node->left;
                    node->left = NULL;
                    free(node);
                    node = NULL;
                }
            }
        }
        //情况二-2
        else if(node->left == NULL && node->right != NULL){
            if(pre == NULL){
                root = node->right;
                node->right = NULL;
                free(node);
                node = NULL;
            }
            else{
                if(node == pre->left){
                    pre->left = node->right;
                    node->right = NULL;
                    free(node);
                    node = NULL;
                }
                else{
                    pre->right = node->right;
                    node->right = NULL;
                    free(node);
                    node = NULL;
                }
            }
        }
        
        //情况三
        else{
            Node *x = node->left;//这里使用上述的方法一,找左子树的最右节点
            Node *prex = node;//节点x的父亲节点,用于改变节点指针连通和赋值
            while(x->right!=NULL){
                prex = x;
                x = x->right;//一直往右走,中序序列就是这样的
            }
            node->data = x->data;
            if(prex == node){
                //如果prex是根节点,即node没有右子树,只有左子树,找到的最右节点就是x,即node->left
                node->left = x->left;
                free(x);
                x = NULL;
            }
            else{
                //存在右子树也找到了右子树中的最右节点x
                prex->right = NULL;
                free(x);
                x = NULL;
            }
        }
    }
}

上述代码为了描述更直观,将左右孩子节点的命名改为了left和right,但是为了操作方便和上一个二叉搜索树的代码对接,这里再统一改一下命名(视频里上下节老师的存储结构设置的不一样)

//删除值为k的节点
void delete(int key){
    Node *node = root;
    Node *pre = NULL;
    if(node!=NULL){
        //寻找节点
        while(node->data != key){
            pre = node;
            if(key < node->data){
                node = node->l;
            }
            else if(key > node->data){
                node = node->r;
            }
            if(node==NULL){
                printf("查无此数据\n");
                return;
            }
        }
        //编译异常说明node指向空后无值,无法调用(如果不加最后的if判断的话)
        if(node->l == NULL && node->r == NULL){
            //情况一,度为0的节点
            if(pre==NULL){
                //若node的父亲为空,那么node为根节点
                root = NULL;
                free(node);
                node = NULL;//野指针
            }
            else{
                //pre不为空,说明找到了对应的node->data == key
                if(pre->l == node){
                    //还要判断node是pre节点的左孩子还是右孩子
                    pre->l = NULL;
                    free(node);
                    node == NULL;
                }
                else{
                    pre->r == NULL;
                    free(node);
                    node = NULL;
                }
            }
        }
        
        //情况二-1 找到的node,node的度为1,找到了该节点的左孩子非空,没有右孩子
        else if(node->l != NULL && node->r == NULL){
            if(pre == NULL){
                //仍然是判断是否删除的为根节点
                root = node->l;
                node->l = NULL;
                free(node);
                node = NULL;
            }
            else{
                //再次判断node节点属于是pre的左孩子还是右孩子
                if(node == pre->l){
                    pre->l = node->l;
                    node->l = NULL;
                    //针对node的左孩子的继承(pre->left = node->left)
                    free(node);
                    node = NULL;
                }
                else{
                    pre->r = node->l;
                    //针对node的左孩子的继承(pre->right = node->left)
                    node->l = NULL;
                    free(node);
                    node = NULL;
                }
            }
        }
        //情况二-2找到的node,node的度为1,找到了该节点的右孩子非空,没有左孩子
        else if(node->l == NULL && node->r != NULL){
            //下面的代码和上面的内容基本相似
            if(pre == NULL){
                root = node->r;
                node->r = NULL;
                free(node);
                node = NULL;
            }
            else{
                if(node == pre->l){
                    pre->l = node->r;
                    node->r = NULL;
                    free(node);
                    node = NULL;
                }
                else{
                    pre->r = node->r;
                    node->r = NULL;
                    free(node);
                    node = NULL;
                }
            }
        }
        
        //情况三 找到的node节点的度为2 使用方法一处理该内容 即寻找node左子树的最右节点
        else{
            //这里设置新的指针进行赋值是必要的,因为要保留node节点的位置,删除的是其代替的节点
            Node *x = node->l;//这里使用上述的方法一,找左子树的最右节点
            Node *prex = node;//节点x的父亲节点,用于改变节点指针连通和赋值
            while(x->r!=NULL){
                prex = x;
                x = x->r;//一直往右走,中序序列就是这样的
            }
            node->data = x->data;
            if(prex == node){
                //如果prex是根节点,即node没有右子树,只有左子树,找到的最右节点就是x,即node->left
                node->l = x->l;
                free(x);
                x = NULL;
            }
            else{
                //存在右子树也找到了右子树中的最右节点x
                prex->r = NULL;
                free(x);
                x = NULL;
            }
        }
    }
}

然后把它再加入到二叉搜索树的整体代码中,回顾“二叉排序树概述及其插入操作”章节末代码

#include <stdio.h>
#include <stdlib.h>
​
//树节点
typedef struct BTNode{
    struct BTNode *l;
    struct BTNode *r;
    int data;
}Node;
Node *root = NULL;//根指针
​
int SearchKey(int key){
    Node *p = root;
    //不使用递归
    while(p!=NULL){
        if(p->data==key){
            //找到key,返回1
            return 1;
        }
        else if(p->data > key){
            p = p->l;
        }
        else{
            p = p->r;
        }
    }
    //没有找到key,返回0
    return 0;
}
​
//插入一个数据x
void insert(int x){
    Node *p = root;
    Node *pre = NULL;//一直指向p的双亲结点
    while(p!=NULL){
        //通过while循环找到插入位置
        pre = p;
        if(x < p->data){
            p = p->l;
        }
        else if(x > p->data){
            //这里必须使用else if,因为在这个while代码块中我们一直在变动指针p的位置,如果再进行x的判断可能会出现p为NULL的情况
            p = p->r;
        }
    }
    //核心仍然是查找
    if(x < pre->data){
        pre->l = (Node*)malloc(sizeof(Node));
        pre->l->data = x;
        pre->l->l = NULL;
        pre->l->r = NULL;
    }
    //这里使用if或者else if就没什么差别了,用了else少进行一次判断,因为这里的pre不会再发生改变
    else if(x > pre->data){
        pre->r = (Node*)malloc(sizeof(Node));
        pre->r->data = x;
        pre->r->l = NULL;
        pre->r->r = NULL;
    }
}
​
//中序遍历二叉搜索树
void inshow(Node *root)
{
    if(root==NULL){
        return;
    }
    if(root->l != NULL){
        inshow(root->l);
    }
    printf("%d ",root->data);
    if(root->r != NULL){
        inshow(root->r);
    }
}
​
//先初始化一个根节点用于后续操作
void initTree(int key){
    root = (Node*)malloc(sizeof(Node));
    root->data = 3;
    root->l = NULL;
    root->r = NULL;
}
​
//删除值为k的节点
void deleteNode(int key){
    Node *node = root;
    Node *pre = NULL;
    if(node!=NULL){
        //寻找节点
        while(node->data != key){
            pre = node;
            if(key < node->data){
                node = node->l;
            }
            else if(key > node->data){
                node = node->r;
            }
            if(node==NULL){
                printf("查无此数据\n");
                return;
            }
        }
        //编译异常说明node指向空后无值,无法调用(如果不加最后的if判断的话)
        if(node->l == NULL && node->r == NULL){
            //情况一,度为0的节点
            if(pre==NULL){
                //若node的父亲为空,那么node为根节点
                root = NULL;
                free(node);
                node = NULL;//野指针
            }
            else{
                //pre不为空,说明找到了对应的node->data == key
                if(pre->l == node){
                    //还要判断node是pre节点的左孩子还是右孩子
                    pre->l = NULL;
                    free(node);
                    node == NULL;
                }
                else{
                    pre->r == NULL;
                    free(node);
                    node = NULL;
                }
            }
        }
        
        //情况二-1 找到的node,node的度为1,找到了该节点的左孩子非空,没有右孩子
        else if(node->l != NULL && node->r == NULL){
            if(pre == NULL){
                //仍然是判断是否删除的为根节点
                root = node->l;
                node->l = NULL;
                free(node);
                node = NULL;
            }
            else{
                //再次判断node节点属于是pre的左孩子还是右孩子
                if(node == pre->l){
                    pre->l = node->l;
                    node->l = NULL;
                    //针对node的左孩子的继承(pre->left = node->left)
                    free(node);
                    node = NULL;
                }
                else{
                    pre->r = node->l;
                    //针对node的左孩子的继承(pre->right = node->left)
                    node->l = NULL;
                    free(node);
                    node = NULL;
                }
            }
        }
        //情况二-2找到的node,node的度为1,找到了该节点的右孩子非空,没有左孩子
        else if(node->l == NULL && node->r != NULL){
            //下面的代码和上面的内容基本相似
            if(pre == NULL){
                root = node->r;
                node->r = NULL;
                free(node);
                node = NULL;
            }
            else{
                if(node == pre->l){
                    pre->l = node->r;
                    node->r = NULL;
                    free(node);
                    node = NULL;
                }
                else{
                    pre->r = node->r;
                    node->r = NULL;
                    free(node);
                    node = NULL;
                }
            }
        }
        
        //情况三 找到的node节点的度为2 使用方法一处理该内容 即寻找node左子树的最右节点
        else{
            //这里设置新的指针进行赋值是必要的,因为要保留node节点的位置,删除的是其代替的节点
            Node *x = node->l;//这里使用上述的方法一,找左子树的最右节点
            Node *prex = node;//节点x的父亲节点,用于改变节点指针连通和赋值
            while(x->r!=NULL){
                prex = x;
                x = x->r;//一直往右走,中序序列就是这样的
            }
            node->data = x->data;
            if(prex == node){
                //如果prex是根节点,即node没有右子树,只有左子树,找到的最右节点就是x,即node->left
                node->l = x->l;
                free(x);
                x = NULL;
            }
            else{
                //存在右子树也找到了右子树中的最右节点x
                prex->r = NULL;
                free(x);
                x = NULL;
            }
        }
    }
}
​
//主函数
int main()
{
    initTree(3);
    inshow(root);
    printf("\n");
​
    insert(1);
    inshow(root);
    printf("\n");
​
    insert(2);
    inshow(root);
    printf("\n");
​
    insert(6);
    inshow(root);
    printf("\n");
​
    insert(5);
    inshow(root);
    printf("\n");
​
    insert(4);
    inshow(root);
    printf("\n");
​
    deleteNode(5);
    inshow(root);
    printf("\n");
    
    return 0;
}

可以通过在main函数中调用函数实现一下各种功能,可自行添加代码用于测试

上述程序运行结果为:

3

1 3

1 2 3

1 2 3 6

1 2 3 5 6

1 2 3 4 5 6

1 2 3 4 6

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值