数据结构-二叉搜索树/二叉排序树/二叉查找树

概述


本文介绍数据结构–二叉搜索树

1. 二叉搜索树基本概念


1.1 定义

二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。。设x为二叉查找树中的一个节点,x节点包含关键字key,节点x的key值记为key[x]。如果y是x的左子树中的一个节点,则key[y] <= key[x];如果y是x的右子树的一个节点,则key[y] >= key[x]。

1.2 性质

二叉搜索树,可以是一棵空树,或者是具有下列性质的二叉树:

  1. 若左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  2. 若右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  3. 左、右子树也分别为二叉排序树;
  4. 没有键值相等的节点。

2. 二叉搜索树相关操作

2.1 二叉搜索树创建

参考- 数据结构与算法——平衡二叉树
现有序列:A = {61, 87, 59, 47, 35, 73, 51, 98, 37, 93}。根据此序列构造二叉搜索树过程如下:
  (1)i = 0,A[0] = 61,节点61作为根节点;
  (2)i = 1,A[1] = 87,87 > 61,且节点61右孩子为空,故81为61节点的右孩子;
  (3)i = 2,A[2] = 59,59 < 61,且节点61左孩子为空,故59为61节点的左孩子;
  (4)i = 3,A[3] = 47,47 < 59,且节点59左孩子为空,故47为59节点的左孩子;
  (5)i = 4,A[4] = 35,35 < 47,且节点47左孩子为空,故35为47节点的左孩子;
  (6)i = 5,A[5] = 73,73 < 87,且节点87左孩子为空,故73为87节点的左孩子;
  (7)i = 6,A[6] = 51,47 < 51,且节点47右孩子为空,故51为47节点的右孩子;
  (8)i = 7,A[7] = 98,98 < 87,且节点87右孩子为空,故98为87节点的右孩子;
  (9)i = 8,A[8] = 93,93 < 98,且节点98左孩子为空,故93为98节点的左孩子;
 创建完毕后如图2.4中的二叉搜索树:
  在这里插入图片描述

2.2 二叉搜索树查找

2.2.1 查找步骤
  1. 如果树是空的,则查找不成功。
  2. 若根结点的关键字值等于查找的关键字,成功。
  3. 否则:
    2.1 若小于根结点的关键字值,递归查左子树。若子树为空,查找不成功。
    2.2 若大于根结点的关键字值,递归查右子树。若子树为空,查找不成功。
2.2.2 查找性能分析

每个结点的 C ( i ) C(i) C(i)为该结点的层次数。

  • 最坏情况下,当先后插入的关键字有序时,构成的二叉排序树蜕变为单支树,树的深度为其平均查找长度 ( n + 1 ) / 2 ^{(n+1)}/_2 (n+1)/2(和顺序查找相同)
  • 最好的情况是二叉排序树的形态和折半查找的判定树相同,其平均查找长度和 l o g 2 ( n ) log_2(n) log2(n)成正比。

查找算法代码:其实与二叉树递归遍历算法的思想是非常相似的。

    /* 递归查找二叉排序树T中是否存在key, */
    /* 指针f指向T的双亲,其初始调用值为NULL */
    /* 若查找成功,则指针p指向该数据元素节点,并返回TRUE */
    /* 否则指针p指向查找路径上访问的最后一个节点并返回FALSE */

    bool searchBST(BSTNode* T, int key, BSTNode* f, BSTNode **p)
    {  
        if (!T) /*  查找不成功 */
        { 
            *p = f;  
            return false; 
        }
        else if (key == T->key) /*  查找成功 */
        { 
            *p = T;  
            return true; 
        } 
        else if (key < T->key) 
            return searchBST(T->lchild, key, T, p);  /*  在左子树中继续查找 */
        else  
            return searchBST(T->rchild, key, T, p);  /*  在右子树中继续查找 */
    }

使用二叉搜索树可以提高查找效率,其平均时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n)

2.3 二叉搜索树插入

插入过程:

  1. 先检测该元素是否在树中已经存在。如果已经存在,则不进行插入;
  2. 若元素不存在,则进行查找过程,并将元素插入在查找结束的位置
    在这里插入图片描述
    在这里插入图片描述

插入代码算法

    void insertBST(BSTNode **T,int key) //此处使用二重指针是因为要修改指针的指针
    {
        BSTNode *s;
        if(*T==NULL) //到达查找结束位置,再次位置插入元素
        {
            s = (BSTNode*)malloc(sizeof(BSTNode));
            s->key = key;
            s->lchild = NULL;
            s->rchild = NULL;
            *T=s;
        }
        else if(key<(*T)->key)//要插入的值大于当前节点,往左子树搜
            insertBST(&((*T)->lchild),key);
        else if(key>(*T)->key)//大于当前节点,往右子树搜
            insertBST(&((*T)->rchild),key);
    }

2.4 二叉搜索树的删除

在删除之前,首先需要找到此节点,针对不同的结点类型,进行删除不同的操作.

情况
删除方式
删除的节点为叶子节点删除叶子节点的方式最为简单,只需查找到该节点,直接删除即可。
删除的节点只有左子树删除的节点若只有左子树,将节点的左子树替代该节点位置。
删除的节点只有右子树删除的节点若只有右子树,将节点的右子树替代该节点位置。
删除的节点既有左子树又有右子树其流程如下:
  (1)遍历待删除节点的左子树,找到其左子树中的最大key节点(最右下),即删除节点的前驱节点;
  (2)将最大节点代替被删除节点;
  (3)删除左子树中的最大节点;
  (4)左子树中待删除最大节点一定为叶子节点或者仅有左子树。按照之前情形删除即可。

对于左右子树都有的结点,可以采用中序遍历的方式来得到删除结点的前驱和后继结点。选取前驱结点或者后继结点代替删除结点即可。
在这里插入图片描述
例如要删除上图的47结点,删除后:
在这里插入图片描述

删除算法代码:

	// BSTNode* T是,我们通过前面的查找算法找到的结点,它不一定就是要删除的结点。
	// 因此,我盟在删除之前需要通过key是否相等,来判断,返回的结点,是不是要删除的结点。
	
    //若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素节点
    //并返回TRUE;否则返回FALSE。
    bool deleteBST(BSTNode* T,int key)
    { 
        if(!*T) /* 不存在关键字等于key的数据元素 */ 
            return false;
        else
        {
            if (key == (*T)->key) /* 找到关键字等于key的数据元素 */ 
                return deleteBSTNode(T);
            else if (key<(*T)->key)
                return deleteBST(&(*T)->lchild,key);
            else
                return deleteBST(&(*T)->rchild,key);

        }
    }
    /* 从二叉排序树中删除节点p,并重接它的左或右子树。 */
    bool deleteBSTNode(BSTNode* p)
    {
        BSTNode* 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)->key=s->key; //s指向被删节点的直接前驱(将被删节点前驱的值取代被删节点的值)
            if(q!=*p)
                q->rchild=s->lchild; //重接q的右子树
            else
                q->lchild=s->lchild; //重接q的左子树
            free(s);
        }
        return TRUE;
    }

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值