25. 二叉排序树

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dugudaibo/article/details/79435097

  之前所介绍的查找方法都是对于已经排序号的数组进行查找,这样的数组虽然使用诸如二分查找法这样的方法可以较快的进行查找,但是在数组中插入或者删除元素的时候,为了保持数组中的相对顺序保持不变,会使得插入和删除变慢;对于没有实现进行排序的数组,插入的时候直接在最后一项进行插入即可,删除某一个元素是只需要首先将待删除的元素与数组中的最后一个元素互换一下位置,在改变数组长度(-1),即可实现删除操作,但是对于这样的数组,查找某一个元素是很慢的,只能够使用顺序查找法。

  那有没有一种方法,他的查找速度不慢,并且插入和删除的速度也还可以的呢?有的,这就是二叉排序树。

1. 基本思想

  二叉排序数(Binary Sort Tree)又称为二叉查找树,它或者是一棵空树,或者是具有下列性质的二叉树:

  (1) 若它的左子树不为空,则左子树上所有结点的值均小于它的根结构的值;

  (2) 若它的右子树不为空,则右子树上所有结点的值均大于它的根结构的值;

  (3) 它的左、右子树也分别为二叉排序树(递归)。

具体如下图所示

对于数组的第一个数字,将它作为二叉树的根节点, 105 >70,所以 105 在根节点的右侧;115 > 105 ,所以 115 在 105 的右侧;104 > 70 且 104 < 105,所以 115 在 70 的右侧,同时也在 105 的左侧;67 < 70, 所以 67 在 70 的左侧;46 < 70,且46 < 67, 所以 67 在 70 的左侧,同时也在 67 的左侧;99 > 70,99 < 105,99 < 104,所以 99 在 70 的右侧,在 105 的左侧,在 104 的左侧;111 和 109 同理。

2. 二叉排序树的查找操作

  根据上面得到的二叉树,我们知道通过中序遍历的方式就可以得到排好顺序的数组,其中中序遍历的顺序先左子树,再根节点,再右子树,因此查找操作的代码如下

// 二叉树的二叉链表结点结构定义
typedef struct BiTNode
{
    int data;
    struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

// 递归查找二叉排序树 T 中是否存在 key
// 指针 f 指向 T 的双亲,其初始值调用值为 NULL
// 若查找成功,则指针 p 指向该数据元素结点,并返回 TRUE
// 否则指针 p 指向查找路径上访问的最后一个结点,并返回 FALSE
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p)
{
    if( !T )    // 表示这个树已经没有继续可以查找的节点了,即查找不成功
    {
        *p = f;
        return FALSE;
    }
    else if( key == T->data )   // 查找成功
    {
        *p = T;
        return TRUE;
    }
    else if( key < T->data )
    {
        return SearchBST( T->lchild, key, T, p );   // 在左子树继续查找
    }
    else
    {
        return SearchBST( T->rchild, key, T, p );   // 在右子树继续查找
    }
}

3. 二叉排序树的插入操作

  在这里有一个变量是需要注意的,就是指针 *p 。在上面的查找操作中,如果可以找到 key 那么指针 *p 就指向查找到的元素;如果没有找到,指针 p 指向查找路径上访问的最后一个结点。也就是说指针始终指向最接近 key 的位置。

// 当二叉排序树 T 中不存在关键字等于 key 的数据元素时,
// 插入 key 并返回 TRUE,否则返回 FALSE
Status InsertBST(BiTree *T, int key)
{
    BiTree p, s;
    if( !SearchBST(*T, key, NULL, &p) )
    {
        s = (BiTree)malloc(sizeof(BiTNode));
        s->data = key;
        s->lchild = s->rchild = NULL;

        if( !p )       
        {
            *T = s;     // 插入 s 为新的根结点,这个时候相当于不存在树
        }
        else if( key < p->data )
        {
            p->lchild = s;  // 插入 s 为左孩子
        }
        else
        {
            p->rchild = s;  // 插入 s 为右孩子
        }
        return TRUE;
    }
    else
    {
        return FALSE;   // 树中已有关键字相同的结点,不再插入
    }
}

4. 二叉排序树的删除操作

  加入我们需要在如下的一棵树中删除某一元素 a,我们可以分为三种情况进行讨论。

  (1) 如果删除的结点是叶子结点,直接删除就可以了;

  (2) 如果删除的结点只有左子树或者只有右子树,比如说 67 或者 115 ,对于这种结点,直接把它的子树接到被删除的结点即可;

  (3) 如果删除的结点既有左子树又有右子树,比如说105,这个时候是不能直接将左子树或者右子树接上去的,因为将 100 接上去的话,115 就没有位置可以放了;同理将 115 接上去的话,100 又没有位置可以放了。这个时候可以将该节点的前驱结点或者后继节点放在被删除的结点的位置,这样就可以满足要求了。

  在上面的两种情况中,前两种情况可以看作是同一种情况。因为只有叶子结点可以看作只有一个左子树为空的结点,所以这两情况在实现的过程中可以采用同样的方法进行实现。

4.1 代码

// 通过递归执行删除
// 也可以使用之前编写的查找的方法,找到就删除,找不到就不删除
Status DeleteBST(BiTree *T, int key)
{
    if( !*T )  //如果结点已经为空了,就是在这个 key 在二叉排序树中找不到,返回错误
    {
        return FALSE;
    }
    else
    {
        if( key == (*T)->data )
        {
            return Delete(T);     //找到 key 进行删除
        }
        else if( key < (*T)->data )
        {
            return DeleteBST(&(*T)->lchild, key);  //在左子树中继续寻找
        }
        else
        {
            return DeleteBST(&(*T)->rchild, key); //在左子树中继续寻找
        }
    }
}

Status Delete(BiTree *p)
{
    BiTree q, s;    // q 是待删除的双亲结点,s 是每一次迭代用到的结点

    if( (*p)->rchild == NULL )    // 如果结点的右子树为空
    {
        q = *p;                //将待删除的结点赋值给 q
        *p = (*p)->lchild;     //将左子树接过来(因为右子树为空嘛,所以接左子树)
        free(q);               // 释放要删除的元素
    }
    else if( (*p)->lchild == NULL )    //与上述同理
    {
        q = *p;
        *p = (*p)->rchild;
        free(q);
    }
    else
    {
        q = *p;
        s = (*p)->lchild;

        while( s->rchild )     //用循环找到该节点的直接前驱,也就是说找到左子树中的最大值(左子树中最右的那一颗树)
        {
            q = s;             // q 每次指向他的上一层,也就是它的双亲
            s = s->rchild;     // s 每一次向下走一步
        }

        (*p)->data = s->data;  // 将欲删除点的直接前驱结点的数据带入被删除结点,只是带入了数据

        if( q != *p )          
        {
            q->rchild = s->lchild;     // 因为 s 是左子树中最右的部分,所以它不会再存在右子树,所以将他的最子树复制过来
        }
        else                           // s 没有右子树,不会执行 while 循环
        {
            q->lchild = s->lchild;     
        }

        free(s);
    }

    return TRUE;
}

4.2 代码解释

  在上述的代码中,比较难懂的是 if( q != *p ) 判断的那个部分。这部分的代码可以通过画图的方法进行解释。

  对于满足if( q != *p )的情况,实际上是图中去掉 105 结点的那种情况。如下图所示,在这种情况下,p 是 105 的那个点,q 是 100 那个点,s 是 104 那个点,将 s 的值给 p 的值,之后将 s 左子树放到 q 的右子树

  对于不满足if( q != *p )的情况,即 s 点不存在右子树,不会进入 while 循环更新 s ,实际上是图中去掉 100 结点的那种情况。如下图所示,在这种情况下,p 是 100 的那个点,q 是 100 那个点,s 是 99 那个点,将 s 的值给 p 的值,之后将 s 左子树放到 q 的左子树

猜你喜欢

转载自blog.csdn.net/dugudaibo/article/details/79435097