深入学习二叉树 二叉排序树

Posted 数据结构与算法

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入学习二叉树 二叉排序树相关的知识,希望对你有一定的参考价值。


作者丨进击的Hello_World
https://www.jianshu.com/p/bbe133625c73


1.前言


数据结构中,线性表分为无序线性表和有序线性表。


无序线性表的数据是杂乱无序的,所以在插入和删除时,没有什么必须遵守的规则,可以插入在数据尾部或者删除在数据尾部。但是在查找的时候,需要遍历整个数据表,导致无序线性表的查找效率低。


有序线性表的数据则相反,查找数据时的时候因为数据是有序的,可以用
二分法、插值法、斐波那契查找法来实现。但是,当进行插入和删除操作时,需要维护表中数据的有序性,会耗费大量的时间。


那么,我们希望找到一种数据结构,既可以有较高的插入和删除效率,并且具备较高的查找效率,因此,二叉排序树应运而生。


深入学习二叉树(四) 二叉排序树

2.二叉排序树


深入学习二叉树(四) 二叉排序树2.1 定义


二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),也称二叉搜索树。二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:


(1)若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
(3)左、右子树也分别为二叉排序树;


深入学习二叉树(四) 二叉排序树2.2 构造一棵二叉排序树


现有序列:61 87 59 47 35 73 51 98 37 93

构造过程如下:


1)索引 i = 0,A[i] = 61,结点61作为根结点,如图2.1:


深入学习二叉树(四) 二叉排序树


2)索引 i = 1,A[1] = 87, 87 > 61,且结点61右孩子为空,故81为61结点的右孩子,如图2.2:


深入学习二叉树(四) 二叉排序树


3)索引 i = 2,A[i] = 59,59 <
61,且结点61左孩子为空,故59为61结点的左孩子,如图2.3:


深入学习二叉树(四) 二叉排序树


4)索引 i = 3,A[3] = 47,47 < 59,且结点59左孩子为空,故47为59结点的左孩子,如图2.4:


深入学习二叉树(四) 二叉排序树


5)索引 i = 4,A[4] = 35,35 < 47,且结点47左孩子为空,故35为47结点的左孩子,如图2.5:


深入学习二叉树(四) 二叉排序树


采用同样规则遍历整个数组得到如图2.6所示的一棵排序二叉树。


深入学习二叉树(四) 二叉排序树


深入学习二叉树(四) 二叉排序树2.3 二叉排序树查找


由二叉树的递归定义性质,二叉排序树的查找同样可以使用如下递归算法查找。


如果树是空的,则查找结束,无匹配。
如果被查找的值和根结点的值相等,查找成功。否则就在子树中继续查找。如果被查找的值小于根结点的值就选择左子树,大于根结点的值就选择右子树。


在理想情况下,每次比较过后,树会被砍掉一半,近乎折半查找。
遍历打印可以使用中序遍历,打印出来的结果是从小到大的有序数组。
查找代码:


typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ 

/* 二叉树的二叉链表结点结构定义 */
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);  /*  在右子树中继续查找 */
}


对于图2.6所示的二叉排序树,若查找结点key为47则可以查找成功,若查找结点key为75,树中不存在key为75的结点,故查找失败,则查找指针p指向查找路径的最后一个结点,即结点73。


深入学习二叉树(四) 二叉排序树2.4 二叉排序树插入


二叉排序的插入是建立在二叉排序的查找之上的,插入一个结点,就是通过查找发现该结点合适插入位置,把结点直接放进去。 其实在2.2节中一步步构造二叉排序树的过程中就是结点插入过程。由此可以得出二叉排序树插入规则如下:


若查找的key已经有在树中,则p指向该数据结点。
若查找的key没有在树中,则p指向查找路径上最后一个结点。


例如:若在图2.6展示的二叉排序树中插入结点数据为60的结点。


首先查找结点数据为60的结点,二叉排序树中不存在结点为60的结点,因此查找失败。此时查找指针p指向查找路径最后一个结点即指向59结点。由于60>59且59结点右子树为空,故将60结点作为59结点的右孩子,插入完成。插入后的二叉排序树如图2.8所示。


深入学习二叉树(四) 二叉排序树


插入代码:


struct BiTree {
    int data;
    BiTree *lchild;
    BiTree *rchild;
};

//在二叉排序树中插入查找关键字key
BiTree* InsertBST(BiTree *t,int key)
{
    if (t == NULL)
    {
        t = new BiTree();
        t->lchild = t->rchild = NULL;
        t->data = key;
        return t;
    }

    if (key < t->data) 
        t->lchild = InsertBST(t->lchild, key);
    else
        t->rchild = InsertBST(t->rchild, key);

    return t;
}

//n个数据在数组d中,tree为二叉排序树根
BiTree* CreateBiTree(BiTree *tree, int d[], int n)
{
    for (int i = 0; i < n; i++)
        tree = InsertBST(tree, d[i]);
}


深入学习二叉树(四) 二叉排序树2.5 二叉排序树删除


二叉树的删除可不再像二叉树的插入那么容易了,以为删除某个结点以后,会影响到树的其它部分的结构。


删除的时候需要考虑以下几种情况:


1)删除结点为叶子结点;
2)删除的结点只有左子树;
3)删除的结点只有右子树
4)删除的结点既有左子树又有右子树。


考虑前三种情况,处理方式比较简单。


例如:若要删除图2.8中的结点93,则直接删除该结点即可。删除后二叉排序树如图2.9所示:


深入学习二叉树(四) 二叉排序树


若要删除的结点为结点35,结点35只有右子树,只需删除结点35,将右子树37结点替代结点35即可。删除后的二叉排序树如图2.10所示:


深入学习二叉树(四) 二叉排序树


删除只有左子树的结点与此情况类似。


情况4相对比较复杂,对于待删除结点既有左子树又有右子树的情形,最佳办法是在剩余的序列中找到最为接近的结点来代替删除结点。这种代替并不会影响到树的整体结构。那么最为接近的结点如何获取呢?


可以采用中序遍历的方式来得到删除结点的前驱和后继结点。选取前驱结点或者后继结点代替删除结点即可。


例如:待删除的结点为47,图2.8中二叉排序树的中序遍历序列为35 37 47 51 59 60 61 73 87 93 98。则结点47的前驱结点为37,则直接将37结点替代47结点即可。替换后的二叉排序树如图2.11所示:


深入学习二叉树(四) 二叉排序树


删除代码:


/* 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素结点, */
/* 并返回TRUE;否则返回FALSE。 */
Status DeleteBST(BiTree *T,int key)

    if(!*T) /* 不存在关键字等于key的数据元素 */ 
        return FALSE;
    else
    {
        if (key==(*T)->data) /* 找到关键字等于key的数据元素 */ 
            return Delete(T);
        else if (key<(*T)->data)
            return DeleteBST(&(*T)->lchild,key);
        else
            return DeleteBST(&(*T)->rchild,key);

    }
}
/* 从二叉排序树中删除结点p,并重接它的左或右子树。 */
Status Delete(BiTree *p)
{
    BiTree q,s;
    if((*p)->rchild==NULL/* 右子树空则只需重接它的左子树(待删结点是叶子也走此分支) */
    {
        q=*p; *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;
            s=s->rchild;
        }
        (*p)->data=s->data; /*  s指向被删结点的直接前驱(将被删结点前驱的值取代被删结点的值) */
        if(q!=*p)
            q->rchild=s->lchild; /*  重接q的右子树 */ 
        else
            q->lchild=s->lchild; /*  重接q的左子树 */
        free(s);
    }
    return TRUE;
}


3.结语


二叉排序树是一种查找与插入效率均较为高效的数据结构,同时,二叉排序树也是二叉树学习中的重点与难点。希望通过本篇的学习能够掌握二叉排序树的查找、插入与删除等基本操作,也希望读者给出指导意见。


 推荐↓↓↓ 

以上是关于深入学习二叉树 二叉排序树的主要内容,如果未能解决你的问题,请参考以下文章

树の讲解-----二叉树入门(遍历)

python判断二叉树是否为平衡二叉树

重温数据结构:深入理解红黑树

二叉树,二叉排序树,红黑树 学习

二叉树入门学习

Java学习-排序二叉树性能简单测试