二叉查找树详解

     二叉查找树(Binary Sort Tree)又称二叉排序树。 它或者是一棵空树;或者是具有下列性质的二叉树: (1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值; (2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值; (3)左、右子树也分别为二叉排序树;
    
    由二叉查找树的性质可以知道,我们如果以二叉树的中序遍历一颗二叉查找树的话,可以得到顺序的数字。下面先给出二叉排序树的常用操作代码,然后在后面再具体讨论其中的一些细节。
    c语言代码如下:
# include <stdio.h>
# include <malloc.h>
# include <stdlib.h>

typedef struct binary{
    int data;
    struct binary *left;
    struct binary *right;
}binaryTree;

void insertBinarySearch(binaryTree * head,int data);
void createBinarySearch(binaryTree * head,int *arr,int n);
void traverseMiddle(binaryTree * head);
binaryTree * findBinarySearch(binaryTree * head,int data);
void deleteBinarySearch(binaryTree * head,int data);

int main(void){
    int arr[10] = {22,17,34,19,30,27,45,9,32,21};
    binaryTree head;
    binaryTree * num; //保存找到的指针节点

    createBinarySearch(&head,arr,10);
    traverseMiddle(&head);

    num = findBinarySearch(&head,28);
    if(num!=NULL){
        printf("查找成功");
    }else{
        printf("查找失败");
    }

    printf("\n");
    deleteBinarySearch(&head,22);
    traverseMiddle(&head);
    return 0;
}

//向二叉查找树中新加入节点
void insertBinarySearch(binaryTree * head,int data){
    binaryTree *h,*p,*parent; //h是头节点,p是新申请的内存节点,parent保存的是新申请节点的父节点
    p = (binaryTree *)malloc(sizeof(binaryTree));  //申请新加入二叉排序树的节点的内存分配
    p->data = data;
    p->left = p->right = NULL;
    h = head;
    //找到需要对应插入的节点的父节点
    while(h){
        parent = h;  //parent保存它的父节点
        if(h->data > data){
            h = h->left;
        }else{
            h = h->right;
        }
    }

    //根据data的值判断是插入在parent节点的左边还是右边
    if(parent->data > data){
        parent->left = p;
    }else{
        parent->right = p;
    }
}    

//创建一个完整的二叉查找树
void createBinarySearch(binaryTree * head,int *arr,int n){
    int i;
    head->data = arr[0];  //把第一个节点拿出来单独插入
    head->left = head->right = NULL;
    //把其它对应的节点插入二叉查找树中
    for(i=1;i<n;i++){
        insertBinarySearch(head,arr[i]);
    }
}

//中序遍历查看二叉查找树
void traverseMiddle(binaryTree * head){
    if(head!=NULL){
        traverseMiddle(head->left);
        printf("%d ",head->data);
        traverseMiddle(head->right);
    }
}

//在二叉查找树中进行查找
binaryTree * findBinarySearch(binaryTree * head,int data){
    //循环找到对应的节点
    while(head != NULL){
        if(head->data>data){   //如果要查找的值比对应的节点小,就去它的左子树找
            head = head->left;
        }else if(head->data<data){   //如果要查找的值比对应的节点大,就去它的右子树找
            head = head->right;   
        }else{
            return head;    //如果相等返回对应的节点
        }
    }
    return NULL;
}

//二叉树的删除操作
void deleteBinarySearch(binaryTree * head,int data){
    int flag = 1;  //标志位,用来判断被删除节点是其父节点的左子树还是右子树    1表示左子树 ,2表示右子树
    binaryTree *h,*parent;  //parent用来保存被删除节点的父节点
    binaryTree *rparent,*r; //这里2个指针的作用是在删除的时候左右子树都不为空的时候使用
    if(head == NULL){
        return ;
    }
    h = head;
    parent = h;
    while(h != NULL){
        if(h->data > data){           //向左子树中去查找
            flag = 1;
            parent = h;
            h = h->left;
        }else if(h->data < data){       //向右子树中去查找
            flag = 2;
            parent = h;
            h = h->right;
        }else if(h->data == data){            //开始删除操作    
            if(h->left == NULL && h->right == NULL){  //如果左子树和右子树都为空
                
                if(h == head){   //如果是根节点
                    free(h);
                }else if(flag == 1){     //如果被删除节点是其父节点的左子树,则让父节点的left设置为NULL
                    parent->left = NULL;
                    free(h);
                }else if(flag == 2){     //如果被删除节点是其父节点的右子树,则让父节点的right设置为NULL
                    parent->right = NULL;
                    free(h);    
                }
            }else if(h->left == NULL && h->right != NULL){  //左子树为空,右子树不为空的情况
                if(flag == 1){   //如果它是其父节点的左子树
                    parent->left = h->right;  //让父节点的左子树指向它的右节点
                }else{
                    parent->right = h->right;  //让父节点的右子树指向它的右节点
                }
                free(h);
            }else if(h->left != NULL && h->right == NULL){  //左子树不为空,右子树为空的情况
                if(flag == 1){
                    parent->left = h->left;
                }else{
                    parent->right = h->left;
                }
                free(h);
            }else if(h->left != NULL && h->right != NULL){  //左右子树都不为空的情况
                rparent = h;   //保存被删除节点的父节点(这里的父亲节点不是h的父节点,而是实际删除的节点)
                r = rparent->right;  //在它的右子树中进行查找
                if(r->left != NULL){  //它的左子树存在,就把左子树的最后一个节点的值换成h的值
                    while(r->left != NULL){
                        rparent = r;
                        r = r->left;
                    }
                    h->data = r->data;
                    rparent->left = NULL;
                    free(r);
                }else{  //它的左子树不存在的话,直接删除r,并且让h的值等于r的值
                    h->data = r->data;
                    h->right = r->right;
                    free(r);
                }
            }    
            //最后一定要把删除的指针设置为NULL
            r = NULL;
            h = NULL;  
        }
    }
}

    由上面的代码可以看到,二叉查找树主要由构建构建二叉查找树,往而二叉查找树中新添加节点,中序遍历,查找节点和删除节点等构成。

    往二叉查找树中添加节点比较简单, 就是不停的遍历这个二叉查找树,如果当前节点的值比需要加入的节点大,就去它的左子树中找,否则去它的右子树中找。当找到它的左子树或者右子树不存在的时候,就在该节点的左子树或者右子树中插入对应的节点。

    二叉查找树的构建是建立在上述添加二叉查找树基础上的,首先让第一个节点加入二叉查找树中,让它成为根节点,然后让其它的节点依次插入。

    前面说过,二叉查找树的中序遍历结果就是按照顺序组成的由大到小的数字。先来看下经过上面的构造的二叉查找树图:

   

 
    中序遍历的结果为:9 17 19 21 22 27 30 32 34 45。

    对于二叉查找树来说,最不好理解的地方还是在于删除的地方。比如在上述图中,如果我要删除17这个节点,那么删除后如何来维护这颗二叉查找树的平衡?

    删除分3种情况:
    1.被删除节点的左子树和右子树都为空,这是最好处理的方法。
    2.被删除节点的左子树或者右子树有一个为空。
    3.被删除节点的左子树和右子树都不为空。

    对于第一种情况和第二种情况,这里不再多说了,上面代码中已经有了注释,这里我们主要来看第三种的情况。需要找出被删除节点中序遍历后的后继节点,该节点的值要比被删除节点的值大,所以只能去它的右子树中去找。而且也要保证这个节点是右子树中最小的,所以必须找到它在右子树中的最小值,所以这个最小值必然在其右子树的最左边。(为什么这样找,看下图就明白了,因为必须保持整个二叉查找树的平衡)。这里可能存在其右子树的左子树不存在的情况,所以需要分别处理。


    假如我们现在要删除的是17这个节点,根据上面的代码可以知道,我们要判断它的右子树19这个节点的左子树存在不,显然这个图中19这个节点的左子树是不存在的,那么我们就直接让17这个节点的值等于19,并把它的右子树指向19这个节点的右子树。这样就可以完成整个二叉查找树的平衡。但是现在我们执行一下下面的代码:
    insertBinarySearch(&head,18);
    我们将18的值也插入二叉查找树中,这个时候的二叉查找树就变成了如下结构:
   
    这个时候就不能依照上面的判断了。 根据上面的说明我们知道,需要把17这个节点的值换成18,让19这个节点的左子树设置为NULL,并且也必须要将18这个节点的值给释放了。(实现过程看代码);这样以后这颗二叉树就变成了:
   
    可以看到这个二叉树还是保持平衡的。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值