二叉排序树(二叉查找树)基本操作_20230417

二叉排序树(二叉查找树)基本操作_20230417

  1. 前言

二叉排序树首先是一颗二叉树,它不同于常规二叉树的地方在于,如果左子树不为空,那么左子树上所有结点的值都不大于根节点的值,如果右子树不为空,那么右子树上所有的值不小于根节点的值,而且它的左右子树本身也属于二叉排序树。

二叉排序树的形式和元素的输入顺序相关,它最坏的情况下可能退化为有序线性表。大多数条件下,二叉排序树既具备二叉树的折半查找行者,又采用了链表作为储存结构,加强了数据储存的灵活性,不失为一种优秀的数据储存结构。

下面的二叉排序树通过中序遍历,就得到一组有序表。

在这里插入图片描述

  1. 二叉排序树的基本操作

2.1 查找操作

二叉排序树的查找操作操作可通过递归实现,由于二叉排序树当中的每个元素都包含有数据域、左孩子指针和右孩子指针,通过递归可以定位到是在左孩子还是右孩子区域进行查找。如果元素比对成功,则返回 true;如果查找失败,则返回false. 如果查找成功,其中的某个递归变量保留查找成功的结点,如果没有找到目标元素,则某个递归变量保留此元素的根节点(父节点)的位置。

查找的实际上是沿着根节点往下遍历的过程,它会形成一颗合适的遍历路径,如果配对成功,路径上的结点都是目标结点的父节点。

看一个具体的例子。给点上述二叉排序树,要求查找元素的值为30,那么遍历形成路径用绿色虚线表示,遍历经过了左–>左–>右的路径。

在这里插入图片描述

元素查找的实现, 如果发现递归结点已经为NULL,意识是查询失败,此二叉排序树当中不含有目标元素,此时查找目标赋值为待查找元素的父节点,同时返回查询失败标记false, false会在退栈过程中不断传递给当前的栈,最终查找函数返回false.

同时,如果当前结点的值和待查找的值相等,意味着本次查询成功,递归可以结束(不再入函数栈),同时返回查询成功的标记true,true会在退栈过程中不断传递给上一级函数栈,最终查找函数返回true。

typedef struct BiTNode
{
    SElemType data;
    struct BiTNode *lchild;
    struct BiTNode *rchild;
} BiTNode, *BiTree;


bool find_bst(BiTree T, KeyType key, BiTree parent, BiTree *target_ptr)
{
    if(T==NULL)
    {
        *target_ptr=parent;
        return false; //one of termination conditions, traveling with parent
    }
    else
    {
        //another condition of termination conditions
        //traveling with parent
        if(EQ(key,T->data.key)) 
        {
            *target_ptr=T;
            return true;
        }
        else if (LT(key, T->data.key))
        {
            return find_bst(T->lchild,key,T,target_ptr);
        }
        else
        {
            return find_bst(T->rchild,key,T,target_ptr);
        }
    }
}

2.2 插入和创建树操作

二叉排序树是一类动态表,其原因在于,如果树中不含有待插入元素,那么二叉排序树会执行插入操作,从而达到动态更新表的目的。插入和创建实际上可以共用一个过程,插入的过程也是创建树的过程。利用上面的查找函数,可以实现插入的过程。正如前面所述,插入过程需要先判断待插入元素是否在现有的表当中,如果不包含在目前的表当中,则需要执行插入操作,并返回插入成功的标记true,否则则直接返回未执行插入的标记false.

bool insert_bst(BiTree *bt, KeyType key)
{
    BiTree ptr;
    BiTree new_node;

    if(!find_bst(*bt,key,NULL,&ptr))
    {
        new_node=(BiTree)malloc(sizeof(BiTNode));
        new_node->data.key=key;
        new_node->data.value=NULL;
        new_node->lchild=NULL;
        new_node->rchild=NULL;

        if(ptr==NULL) // don't leave this condition behind
        {
            *bt=new_node;
        }
        else if(LT(key,ptr->data.key))
        {
            ptr->lchild=new_node;
        }
        else
        {
            ptr->rchild=new_node;
        }

        return true;
    }

    return false;
}

2.3 二叉排序树删除操作

二叉排序树的结点删除分3种情况讨论,

a.) 若P为叶子结点,既PL和PR均为空树,由于删除叶子结点不破坏树的结点,只需要修改P结点的指针即可,也就是*p=NULL即可。

在这里插入图片描述

b.) 上述图,若 P结点只有左子树或只有右子树,此时只要令PL或PR称为父节点的左子树即可(也即是把指针赋值为结点P即可)

c.) 若P结点的左右子树均不为空,如果删除元素P后,需要保持二拆排序树仍然有序,那么就有两种途径,①-a途径,称之为替代法,用p元素的直接前驱元素S里面的值替代P里面的值,P的左右孩子指针保持不变,同时删除S结点,把S结点的左孩子赋值给其双亲结点的右孩子;②-b途径是利用待删除元素的左子树根节点来替代P所在结点,同时把P结点原有的右子树赋值给左子树的最右端元素。

两种类型不同在于①-a利用原有结点的左右孩子指针,只是替代元素;②-b则是直接修改替换原有结点,并更新现有结点的对应指针。

在这里插入图片描述

c) 删除的代码实现

二叉排序树的删除过程仍然采用递归函数,如果找到待删除元素,则执行删除操作,并返回删除成功标记,否则返回删除失败标记。

bool delete_bst(BiTree *T, KeyType key)
{
    //if deletion is succesfful, it will return true;
    //if deletion is not successful, it will return false
    if(*T==NULL)
    {
        return false;  // one termination condition
    }
    else
    {
        if(EQ(key,(*T)->data.key))
        {
            delete_action_b(T); //propagate the return value
            return true; //the second termination condition
        }
        else if (LT(key, (*T)->data.key))
        {
            return delete_bst(&((*T)->lchild),key);
        }
        else
        {
            return delete_bst(&((*T)->rchild), key);
        }
    }
}

分别用两个函数实现不同的删除模式,

//①-a implementation code
void delete_action_a(BiTree *node)
{
    //p;
    //s;
    //list three scenarios of node
    BiTree p;
    BiTree s;

    if((*node)->lchild==NULL)
    {
        p=*node;
        (*node)=(*node)->rchild;
        free(p);
    }
    else if ((*node)->rchild == NULL)
    {
        p = *node;
        (*node) = (*node)->lchild;
        free(p);
    }
    else
    {
        p= *node;
        s=(*node)->lchild; //next one
        while(s->rchild!=NULL)
        {
            p=s;
            s=s->rchild;
        }

        (*node)->data=s->data;

        if(p!=(*node))
        {
            p->rchild=s->lchild;
        }
        else
        {
            p->lchild=s->lchild; //no right child and jumpt one node
        }

        free(s);
    }

    return;
}

//②-b implementation code
void delete_action_b(BiTree *node)
{
    BiTree p;
    BiTree s;


    if ((*node)->lchild == NULL)
    {
        p = *node;
        (*node) = (*node)->rchild;
        free(p);
    }
    else if ((*node)->rchild == NULL)
    {
        p = *node;
        (*node) = (*node)->lchild;
        free(p);
    }
    else
    {
        p = *node;
        s = (*node)->lchild;
      

        while (s->rchild != NULL)
        {
            s = s->rchild;
        }

        s->rchild=(*node)->rchild; // 先后顺序非常重要
        (*node)=(*node)->lchild;  // 先后顺序非常重要

        free(p);
    }

    return;
}
  1. 二叉查找树的形式

与静态二叉搜索树不同,静态二叉搜索树的形式是唯一的;对于相同的元素集合,二叉查找树的形式会随着不同的排列顺序呈现不同的树的形态。由于树的形态不同,造成树的深度不同,导致平均查找长度不同(Average Search Length),如果输入有序元素,二叉查找树就退化为有序线性表,导致极端的情况发生。

在这里插入图片描述

这就为后面平衡二叉查找树的引入提供了应用场景,本文仅针对二叉排序树,不会对AVL树进一步阐述。

  1. 小结

本文学习了二叉排序树的不同操作,包括插入、建树和删除等操作,同时阐述了不同形态的二叉查找树会影响查找效率,极端情况下,有序输入会导致二叉排序树蜕变为线性表,严重影查询效率。

参考资料:

《数据结构》严蔚敏,清华大学

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值