二叉树

本文介绍了二叉排序树的概念、查找过程、插入操作和删除操作。二叉排序树是一种结合数组和链表优点的数据结构,常用于查找和排序。在实际应用中,平衡二叉树如红黑树被广泛使用。文章还提供了查找、插入和删除的代码示例。

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

二叉树

在学习数据结构的时候,除了基本的之外的,还有许多树像是二叉搜索树,2-3树,红黑树等等。

也曾经学习过二叉树,以及前序排列中序排列后序排列等等,但是一直无缘使用它!

排序有快速排序,归并排序,查找有二分法,甚至直接遍历查找,二叉树的使用很少。那二叉树究竟是干什么的呢?

进行了一番粗浅的研究,我们学习的经典二叉树,仅仅当他是一种数据结构是不行的,他还是一种编程思想,例如解决背包问题(后面进行学习),在考试和面试中使用较多。

而实际场景使用上,用的最多的是二叉平衡树,有种特殊的二叉平衡树就是红黑树,Java集合中的TreeSet和TreeMap,C++STL中的set,map以及LInux虚拟内存的管理,都是通过红黑树去实现的,还有哈弗曼树编码方面的应用,以及B-Tree,B+-Tree在文件系统中的应用。当然二叉查找树可以用来查找和排序啦(知乎网友)

那么二叉树有什么优点?

大家看到最多的是这么说的,二叉排序树是一种比较有用的折中方案:

数组的搜索比较方便,可以直接使用下标,但删除或者插入就比较麻烦了,而链表与之相反,删除和插入都比较简单,但是查找很慢,这自然也与这两种数据结构的存储方式有关,数组是取一段相连的空间,而链表是每创建一个节点便取一个节点所需的空间,只是使用指针进行连接,空间上并不是连续的。而二叉树就既有链表的好处,又有数组的优点。

二叉树的分类,了解一下

  • 满二叉树:从高到低,除了叶节点外,所有节点左右节点都存在。

  • 完全二叉树:比满二叉树少几个叶节点,从左向右放子节点。

  • 平衡二叉树:空树,或者它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树也都是平衡树。

  • 二叉排序树:空树,或者二叉树的所有节点比他的左子节点大,比他的右子节点小。

  • 红黑树:不仅是具有二叉搜索树的属性,还具有平衡树的属性,有序且子树差不超过1,颜色规则:根节点和特殊节点(即叶节点下面两个虚无的节点和未填写的节点)是黑的,红节点的左右子节点是黑的,最重要的是对于每个节点,从该节点到子孙叶节点的所有路径包含相同数目的黑节点。

二叉排序树

又称二叉排序树(Binary Sort Tree)、二叉查找树(Binary Search Tree)、二叉搜索树(Binary Search Tree)

1. 二叉排序树定义

空树,或者二叉树的所有节点比他的左子节点大,比他的右子节点小。

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树。

2. 二叉排序树查找过程

查找过程和次优二叉树类似。:折半查找的一种形式。若二叉排序树不空,首先将给定值和根节点的关键值进行比较,若相等,则查找成功;大于,继续在右子树查找;小于,在左子树查找。

代码:

// 利用递归算法实现二叉排序树的查找
// 找到,则返回该数据元素结点的指针,否则,返回空指针。
BinTree SearchBST(BinTree T, KeyType key)
{
    if((!T) || EQ(key, T->data.key) return(T); //空树,直接返回。非空树,等于根,返回。
    else if(LT(key, T->data.key)) return(SearchBST(T->lchild, key)); //小于根,继续在左子树查找
    else return(SearchBST(T->rchild, key)); //大于根,继续在右子树查找
}

3. 二叉排序树插入操作

与次优二叉树相对,二叉排序树是一种动态树表。即:树的结构通常不是一次生成的,而是在查找过程中,当树中不存在关键字等于给定值得结点时在进行插入(找不到时再插入)。

注意:每次插入的新结点,都是树的叶子结点。所以插入时不必移动其他结点,只改变指针由空变为非空即可。

为此,对上节中的算法进行改进,如果找不到该关键值的结点,就返回查找路径上访问的最后一个结点的左孩子或右孩子,用于插入。

代码:

// 查找根指针为 T 的树上存不存在关键值为 key 的结点。f 为 T 的双亲。
// 成功,返回 TRUE,并 p 指向该结点。
// 失败,返回 FALSE, 并 p 指向查找路径上访问的最后一个结点
Status SearchBST(BinTree T, KeyType key, BinTree f, BinTree &p)
{
    if(!T)  { p = f; return FALSE; }//空树,直接返回。
    else if(EQ(key, T->data.key)) { p = T; return TRUE; } //非空树,等于根,返回。
    else if(LT(key, T->data.key)) return(SearchBST(T->lchild, key, T, p)); //小于根,继续在左子树查找
    else return(SearchBST(T->rchild, key, T, p)); //大于根,继续在右子树查找
}

插入算法代码:

// 若根指针为 T 的树上不存在关键值为 e.key 的结点,就插入,并返回TRUE
// 若存在,就返回FALSE
Status InsertBST(BinTree T, ElemType e)
{
    BinTree p;
    BinTNode *s;
    if(!SearchBST(T, e.key, NULL, p))
    {
        s = (BinTNode *)malloc(sizeof(BinTNode));
        s->data = e;
        s->lchild = NULL;
        s->rchild = NULL;
        if(!p) // 空树
            T = &s;
        else if(LT(key, p->lchild)) 
            T->lchild = &s;
        else 
            T->rchild = &s;
        free(s);
        return TRUE; // 插入成功
    }
    else 
        return FALSE; // 找到了, 插入失败
}

注意:中序遍历二叉搜索树可得到一个关键字的有序序列。也就是说,一个无序序列可以通过构造一颗二叉排序树而变成一个有序序列。
采用链表作为存储结构,是动态查找表

4、二叉排序树删除操作

删除 *p 结点, 分 3 种情况讨论(不失一般性,假设 *p 是 *f 的左孩子):

  • *p 是叶子结点:直接删除,改变 *f 的指针即可。
  • *p 只有左子树或只有右子树:将该左子树(或右子树)改为 *f 的左子树即可。
  • *p 的左子树和右子树均不为空:比较复杂,2 种解决方法:
    1. 令 *p 的左子树为 *f 的左子树, 然后 *p 的右子树为 *p 最右边结点的右子树。
    2. 令 *p 的直接前驱( *p 的左子树的最右边结点)直接代替 *p,再删除该直接前驱

代码:

Status Delete(BinTree &p)
{   
    BinTree q; //备份
    if(!p->rchild) // 右子树为空,只需要重接左子树;左右均空,可以放在这里面
    {
        q = p;
        p = p->lchild;
        free(q);
    } 
    else if(!p->lchild) // 左子树为空,只需重接右子树
    {
        q = p;
        p = p->rchild;
        free(q);
    }
    else //左右子树都非空
    {
        // 方法1:(p是f的左孩子)
        // 先将p的右子树接在左子树的最右端
        BinTree s = p->lchild; 
        while(s->rchild) {q = s; s = s->rchild};
        q->rchild = p->rchild;
        // 然后*p的左子树作为f的左子树
        q = p;
        p = p->lchild;
        free(q);

        // 方法2:
        // 首先找到直接前驱
        q = p;
        BinTree s = p->lchild;
        while(s->rchild) {q = s; s = s->rchild}; // s指向直接前驱
        p->data = s->data;
        // 若p即为s的父节点,则p的左孩子就是s的左孩子,否则,s的父节点的右孩子指向s的左孩子
        if(q!=p) q->rchild = s->lchild;
        else q->lchild = s->lchild; 
        // 删除s
        delete s;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值