zoukankan      html  css  js  c++  java
  • 深入学习二叉树(四) 二叉排序树

    摘自: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.1

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

     
    图2.2

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

     
    图2.3

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

     
    图2.4

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

     
    图2.5

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

     
    图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所示。

     
    图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所示:

     
    图2.9

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

     
    图2.10

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

    情况4相对比较复杂,对于待删除结点既有左子树又有右子树的情形,最佳办法是在剩余的序列中找到最为接近的结点来代替删除结点。这种代替并不会影响到树的整体结构。那么最为接近的结点如何获取呢?
    可以采用中序遍历的方式来得到删除结点的前驱和后继结点。选取前驱结点或者后继结点代替删除结点即可。
    例如:待删除的结点为47,图2.8中二叉排序树的中序遍历序列为35 37 47 51 59 60 61 73 87 93 98。则结点47的前驱结点为37,则直接将37结点替代47结点即可。替换后的二叉排序树如图2.11所示:

     
    图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 结语

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

  • 相关阅读:
    88250 的“一分钟想法”
    88250 的“一分钟想法”
    有关在对话框上创建视图上的再探索
    jmap命令(Java Memory Map) 51CTO.COM
    要么滚回家里去,要么就拼
    悲观的思考,乐观的生活.我们既需要思考的深度,也需要生活的温度!
    对研发经理这一岗位的个人理解
    Linux Soho 兼 职 系统工程师 Email: yufeixiaoyu@gmail.com
    9月1日机器学习与自然语言处理精品班开班现场_培乐资讯_北京培乐园科技咨询有限公司
    简单的Memory leak跟踪
  • 原文地址:https://www.cnblogs.com/LiuYanYGZ/p/13695615.html
Copyright © 2011-2022 走看看