二叉排序树的创建与删除(C语言)

本文详细介绍了如何在C语言中创建和处理二叉排序树,包括节点的插入策略以及删除操作中的注意事项,特别强调了为何需要通过地址而非直接传递指针进行修改。讨论了删除操作时可能出现的错误和解决方案,指出直接删除可能导致数据冗余问题。

目录

二叉排序树创建

二叉排序树节点的删除

二叉排序树创建

思路如下:

第一步,首先函数传入的参数为Tree类型的指针的地址以及需要插入的data;

第二步,判断当前节点是否非空,若是空节点则直接申请内存并构造叶子节点,若非空则比较插入节点的data与当前节点的data;

1. 若大于当前节点,则将当前节点的右节点地址以及data传入函数;

2. 若小于当前节点,则将当前节点的左节点地址以及data传入函数;

3.若等于,则返回ERROR;

注:本次代码构建的二叉搜索树左节点始终小于右节点!!!

bool Insert(Tree *T, int data) {
    if(*T == NULL){
        *T = (Tree)malloc(sizeof(struct TNode));
        (*T)->data = data;
        (*T)->left = NULL;
        (*T)->right = NULL;
    }
    else{
        if((*T)->data > data){
            Insert(&((*T)->left), data);
            return true;
        }
        else if(((*T)->data < data)){
            Insert(&((*T)->right), data);
            return true;
        }
        else{
            printf("与当前节点值相等!!!");
            return ERROR;
        }
    }
}

易错提醒:函数不能这样写:bool Insert(Tree T, int data),即为什么不能直接传递Tree指针,反而传递当前指针的地址呢?

简单解释如下:这里涉及C语言的规定,在参数传递过程中,将实参复制给形参,这种传递是单向的,只允许实参把值复制给形参,即使形参的值在函数中发生了变化,也不会影响实参。

相信这个简单例子大家都能理解:

// 交换函数的错误写法
void swap1(int a, int b){
    int tmp = a;
    a = b;
    b = tmp;
}

// 正确写法如下:
void swap2(int *a, int *b){
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

int a = 10, b = 20;
swap1(a,b);
printf("交换前a = 10,b = 20,交换后a = %d,b = %d\n", a, b);
//交换前a = 10,b = 20,交换后a = 10,b = 20

swap2(&a, &b);
printf("交换前a = 10,b = 20,交换后a = %d,b = %d\n", a, b);
//交换前a = 10,b = 20,交换后a = 20,b = 10

同样,对于Insert函数也是如此,需要通过传递节点的地址才能对当前节点进行修改!!!

二叉排序树节点的删除

思路如下:

删除操作通过两个函数进行删除,其中Delete是对指定节点的删除,DeleteN是查找需要删除的节点;

下面讲解Delete函数:

第一步,判断当前节点是否为叶子节点,若是,直接删除,若不是,进入第二步;

第二步,判断当前节点的左子树是否非空(包含左右子树均非空的情况,但只要左子树非空,就选取左子树的最大值),若非空,即寻找左子树的最大值替换需要删除的节点值,若空,进入第三步;

第三步,判断当前节点的左右子树是否非空(只包含左子树空,右子树非空的情况),若非空,即寻找右子树的最小值替换需要删除的节点值;

注:整个删除的过程类似节点的上浮!!!

//对指定节点进行删除
bool Delete(Tree *T){
    if(!(*T)->left && !(*T)->right){
        *T = NULL;
        return true;
    }
    else if((*T)->left){
        Tree pre = *T;//删除操作!!!
        Tree cur = (*T)->left;
        while(cur->right){
            pre = cur;
            cur = cur->right;
        }
        (*T)->data = cur->data;
        //通过记录的前一个,将其进行删除
        if(pre == *T){
            Delete(&(pre->left));
        }
        else{
            Delete(&(pre->right));
        }
        //Delete(&cur);这样有问题
    }
    else if(!(*T)->left && (*T)->right){
        Tree pre = *T;
        Tree cur = (*T)->right;
        while(cur->left){
            pre = cur;
            cur = cur->left;
        }
        (*T)->data = cur->data;
        if(pre == *T){
            Delete(&(pre->right));
        }
        else{
            Delete(&(pre->left));
        }
        // Delete(&cur);这样写有问题
    }
}

//查找需要删除的节点
void DeleteN(Tree *T, int data){
    if(*T == NULL){
        printf("需要删除的节点不存在!!!\n");
        printf("删除失败!!!\n");
        return ;
    }
    if((*T)->data == data){
        Delete(T);
    }
    else if((*T)->data < data){
        DeleteN(&((*T)->right), data);
    }
    else{
        DeleteN(&((*T)->left), data);
    }
}

在代码中,对于删除操作有一行我进行了注释,这是我在实际运行过程中遇到的问题,既然每次删除的节点都是最后得到的cur,那为什么不能直接写Delete(&cur),反而要进行判断多此一举呢?

下面是采用注释代码的运行结果:

//中序遍历如下:
//0       1       2       3       4       5       6       7       8       9
//对元素4进行删除
//中序遍历如下:
//0       1       2       2       3       5       6       7       8       9

可以观察到,对4进行删除的确是成功了,但是可以观察到,数字2出现了两次,为什么呢?

刚才我们说到,删除操作类似元素的向上浮动,而数字2所在的节点为叶子节点,虽然向上浮动将4,3进行了覆盖,但对最终的2所在的叶子节点删除失败了!

对于这个问题,可能与节点指针有关系,我的想法是,如果直接删除,相当于与前一个节点直接割裂了,但是如果通过前一个节点直接指向,就避免了这种行为,以多余的2为例,如果通过前一个节点指向这个节点我们设置为NULL,那遍历的时候自然不会打印当前节点的值,但是如果我们只是把当前节点(cur)设置为NULL,这并不是说明当前节点值就不存在了,通过遍历还是能得到原始的值,这就解释了为什么会有两个2。

如有错误,欢迎评论区批评指正!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

施霁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值