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

本文介绍了二叉排序树的基本操作,包括查找、插入和删除。查找操作通过递归实现,插入操作在查找基础上进行,删除操作涉及三种情况。文章还强调了元素输入顺序对二叉排序树形态和查找效率的影响,以及有序输入可能导致的退化问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二叉排序树(二叉查找树)基本操作_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. 小结

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

参考资料:

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

<think>嗯,用户想了解PTA平台上关于二叉排序树查找操作实现或问题解决。首先,我需要回忆二叉排序树的基本概念和查找算法。二叉排序树,也叫二叉搜索树,它的每个节点都满足左子树上所有节点的值小于根节点的值,右子树上所有节点的值大于等于根节点的值。查找操作通常是递归或迭代地比较目标值和当前节点,然后选择左或右子树继续查找。 接下来,用户提到PTA平台,这可能是指程序设计类实验辅助教学平台,通常会有编程题目。所以用户可能需要具体的代码实现或者解题思路。根据引用[1],二叉查找树查找时间复杂度在最好情况下是O(log n),最坏情况下是O(n),这取决于树的平衡性。所以在实现时,可能需要考虑平衡性优化,但PTA题目可能更侧重基础实现。 然后,我需要检查用户提供的引用内容。引用[1]详细说明了二叉查找树的时间复杂度,引用[2]提到树型选择排序,可能相关但不太直接。引用[3]展示了一个顺序表查找的函数,可能作为对比。用户的问题集中在二叉排序树查找,所以重点应放在如何实现查找操作,包括递归和非递归方法,以及可能的错误处理,比如处理空树或未找到元素的情况。 可能用户需要代码示例,比如C语言的结构体定义、查找函数,以及如何处理返回值。比如PTA题目可能要求返回找到的节点位置或者某种特定格式。此外,解题思路需要包括步骤分析,比如比较关键字,递归查找子树,或者使用循环迭代。 还要考虑常见问题,比如如何处理重复元素(通常放在右子树),以及查找失败时的处理,比如返回NULL或0。根据引用[3],顺序表查找返回位置序号,而二叉排序树可能需要返回节点指针或层级等信息,但具体要看题目要求。 最后,生成相关问题时要围绕二叉排序树查找展开,比如时间复杂度、优化方法、与二分查找对比等,参考用户提供的示例中的问题结构。</think>### 二叉排序树查找操作实现与解题思路 #### 一、二叉排序树查找原理 二叉排序树(BST)的查找操作基于其性质:左子树节点值均小于根节点,右子树节点值均大于等于根节点[^1]。查找算法通过逐层比较目标值与当前节点值,逐步缩小搜索范围,时间复杂度在平衡状态下为$O(\log n)$,最坏情况下退化为$O(n)$。 #### 二、PTA题目实现步骤 1. **数据结构定义** 需先定义二叉排序树的节点结构: ```c typedef struct TreeNode { int data; struct TreeNode *left, *right; } BSTNode; ``` 2. **递归实现查找** 通过递归方式查找目标值: ```c BSTNode* BST_Search(BSTNode* root, int key) { if (root == NULL || root->data == key) return root; if (key < root->data) return BST_Search(root->left, key); else return BST_Search(root->right, key); } ``` 3. **非递归实现查找** 使用循环代替递归,适合深度较大的树: ```c BSTNode* BST_Search_Iter(BSTNode* root, int key) { while (root != NULL && root->data != key) { if (key < root->data) root = root->left; else root = root->right; } return root; } ``` 4. **PTA题型扩展** 若题目要求返回查找路径或层级信息,可增加计数器: ```c int searchLevel(BSTNode* root, int key) { int level = 1; while (root) { if (root->data == key) return level; root = (key < root->data) ? root->left : root->right; level++; } return 0; // 查找失败 } ``` #### 三、典型错误与解决方法 1. **未处理空树**:需在函数入口判断`root == NULL` 2. **忽略重复值**:题目若要求严格小于/大于,需调整条件判断 3. **返回值类型不符**:注意题目要求返回节点指针、位置序号还是布尔值 #### 四、性能优化方向 1. **平衡二叉排序树**:通过AVL树或红黑树减少树高度 2. **路径压缩**:在查找过程中调整树结构(如伸展树) 3. **尾递归优化**:将递归改写为迭代节省栈空间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值