热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【数据结构】之二叉排序树的实现(C语言)

文章目录二叉排序树二叉排序树的查找操作二叉排序树的插入操作二叉排序树的删除操作总结如果我们要查找的数据集是有序线性表,并且是顺序存储的,查找可以用折半&

文章目录

    • 二叉排序树
      • 二叉排序树的查找操作
      • 二叉排序树的插入操作
      • 二叉排序树的删除操作
    • 总结



如果我们要查找的数据集是有序线性表,并且是顺序存储的,查找可以用折半,插值,等查找算法来实现。可惜,因为有序,在插入和删除操作上,就需要耗费大量的时间。

有没有一种既可以使得插入和删除的效率不错,又可以比较高效率的实现查找的算法呢?

二叉排序树

二叉排序树,又称为二叉查找树。它或者是一颗空树,或者是具有下列性质的二叉树。

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

从二叉树的定义也可以知道,它的前提是二叉树,然后它采用了递归的定义方法,再者,它的结点间满足一定的次序关系,左子树结点一定比其双亲结点小,右子树结点一定比其双亲结点大。
构造一颗儿二叉排序树的目的,其实并不是为了排序,而是为了提高查找和插入删除关键字的速度。在一个有序数据集上的查找,速度总是要快于无序的数据集的。而二叉排序树这种非线性结构,也有利于插入和删除的实现。

二叉排序树的查找操作

定义一个二叉树的结构

/* 二叉树的二叉链表结点结构定义 */
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 &#61; f; return FALSE; }else if (key&#61;&#61;T->data) /* 查找成功 */{ *p &#61; T; return TRUE; } else if (key<T->data) return SearchBST(T->lchild, key, T, p); /* 在左子树中继续查找 */else return SearchBST(T->rchild, key, T, p); /* 在右子树中继续查找 */
}

二叉排序树的插入操作

有了二叉排序树的查找函数&#xff0c;那么所谓的二叉排序树的插入&#xff0c;其实就是将关键字放到树中合适的位置而已。

/* 当二叉排序树T中不存在关键字等于key的数据元素时&#xff0c; */
/* 插入key并返回TRUE&#xff0c;否则返回FALSE */
Status InsertBST(BiTree *T, int key)
{ BiTree p,s;if (!SearchBST(*T, key, NULL, &p)) /* 查找不成功 */{s &#61; (BiTree)malloc(sizeof(BiTNode));s->data &#61; key; s->lchild &#61; s->rchild &#61; NULL; if (!p) *T &#61; s; /* 插入s为新的根结点 */else if (key<p->data) p->lchild &#61; s; /* 插入s为左孩子 */else p->rchild &#61; s; /* 插入s为右孩子 */return TRUE;} else return FALSE; /* 树中已有关键字相同的结点&#xff0c;不再插入 */
}

二叉排序树的删除操作

对于删除情况我们要考虑多种情况&#xff0c;并不是那么容易的。我们不能因为删除了结点&#xff0c;而让这棵树变得不满足二叉排序树的特性。
如下图1-1所示&#xff0c;我们需要查找并删除入37,51,73,93&#xff0c;这些在二叉排序树中是叶子结点。那是很容易的&#xff0c;因为删除它&#xff0c;他们呢对于整棵树来说&#xff0c;其他的结点并没有受到影响。
在这里插入图片描述

图1-1

对于要删除的结点只有左子树金额右子树的情况&#xff0c;相对也比较好解决&#xff0c;那就是结点删除之后&#xff0c;将它的左子树和右子树整个移动到删除结点的位置即可&#xff0c;可以理解为子承父业。如下图1-2所示&#xff0c;就是先删除35&#xff0c;和99结点&#xff0c;在删除58结点的变化图&#xff0c;删除完后&#xff0c;整个结构还是一个二叉排序树。
在这里插入图片描述

图1-2

但是对于要删除的结点既有左子树又有右子树的情况怎么办&#xff1f;如图1-3所示中的结点47若要删除&#xff0c;它的两个儿子以及子孙怎么处理了&#xff1f;
在这里插入图片描述

图1-3

起初的想法&#xff0c;我们当47结点只有一个左子树&#xff0c;那么做法和一个左子树的操作一样&#xff0c;让35及它之下的结点成为58的左子树&#xff0c;然后再对47的右子树所有节点进行插入操作&#xff0c;图1-4所示&#xff0c;但是结点47的右子树共有5个子孙结点&#xff0c;这么做效率低下&#xff0c;而且还会导致整个二叉排序树的结构发生很大的变化&#xff0c;有可能会增加树的高度。

在这里插入图片描述

图1-4

观察观察&#xff0c;47的两个子树中能否找出一个结点可以替代47了&#xff1f; 37或者48都可替代。
为什么是37和48了&#xff1f;对的&#xff0c;因为他们正好是二叉排序树中比他小或比他大的最接近47的两个数。
因此较好的办法就是&#xff0c;找到需要删除的结点p的直接前驱或者直接后继s&#xff0c;用s来替换结点p&#xff0c;然后在删除结点s&#xff0c;图1-5所示。
在这里插入图片描述在这里插入图片描述

图1-5

根据对删除结点的三种情况的分析&#xff1a;

  • 叶子节点
  • 仅有左或右子树的结点。
  • 左右子树都有的结点。

/* 从二叉排序树中删除结点p&#xff0c;并重接它的左或右子树。 */
Status Delete(BiTree *p)
{BiTree q,s;if((*p)->rchild&#61;&#61;NULL) /* 右子树空则只需重接它的左子树&#xff08;待删结点是叶子也走此分支) */{q&#61;*p; *p&#61;(*p)->lchild; free(q);}else if((*p)->lchild&#61;&#61;NULL) /* 只需重接它的右子树 */{q&#61;*p; *p&#61;(*p)->rchild; free(q);}else /* 左右子树均不空 */{q&#61;*p; s&#61;(*p)->lchild;while(s->rchild) /* 转左&#xff0c;然后向右到尽头&#xff08;找待删结点的前驱&#xff09; */{q&#61;s;s&#61;s->rchild;}(*p)->data&#61;s->data; /* s指向被删结点的直接前驱&#xff08;将被删结点前驱的值取代被删结点的值&#xff09; */if(q!&#61;*p)q->rchild&#61;s->lchild; /* 重接q的右子树 */ elseq->lchild&#61;s->lchild; /* 重接q的左子树 */free(s);}return TRUE;
}/* 若二叉排序树T中存在关键字等于key的数据元素时&#xff0c;则删除该数据元素结点, */
/* 并返回TRUE&#xff1b;否则返回FALSE。 */
Status DeleteBST(BiTree *T,int key)
{ if(!*T) /* 不存在关键字等于key的数据元素 */ return FALSE;else{if (key&#61;&#61;(*T)->data) /* 找到关键字等于key的数据元素 */ return Delete(T);else if (key<(*T)->data)return DeleteBST(&(*T)->lchild,key);elsereturn DeleteBST(&(*T)->rchild,key);}
}

总结

二叉排序树是链接的方式存储的&#xff0c;保持了链接存储结构在执行插入或删除操作时不用移动元素的优点&#xff0c;只要找到合适的插入和删除位置后&#xff0c;仅需修改链接指针。插入删除的时间性能可能比较好&#xff0c;而对于二叉排序树的查找&#xff0c;走的就是从根结点到要查找的结点的路径&#xff0c;其比较次数等于给定值的结点在二叉排序树的层数。极端情况&#xff0c;最少为一次&#xff0c;即根结点就要是查找的结点&#xff0c;最多也不会超过树的深度&#xff0c;也就是说&#xff0c;二叉排序树的查找性能取决于二叉排序树的形状。问题就在于二叉排序树的形状是不确定的。


推荐阅读
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • Go语言实现堆排序的详细教程
    本文主要介绍了Go语言实现堆排序的详细教程,包括大根堆的定义和完全二叉树的概念。通过图解和算法描述,详细介绍了堆排序的实现过程。堆排序是一种效率很高的排序算法,时间复杂度为O(nlgn)。阅读本文大约需要15分钟。 ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
  • 本文介绍了一道经典的状态压缩题目——关灯问题2,并提供了解决该问题的算法思路。通过使用二进制表示灯的状态,并枚举所有可能的状态,可以求解出最少按按钮的次数,从而将所有灯关掉。本文还对状压和位运算进行了解释,并指出了该方法的适用性和局限性。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文由编程笔记#小编整理,主要介绍了关于数论相关的知识,包括数论的算法和百度百科的链接。文章还介绍了欧几里得算法、辗转相除法、gcd、lcm和扩展欧几里得算法的使用方法。此外,文章还提到了数论在求解不定方程、模线性方程和乘法逆元方面的应用。摘要长度:184字。 ... [详细]
  • 合并列值-合并为一列问题需求:createtabletab(Aint,Bint,Cint)inserttabselect1,2,3unionallsel ... [详细]
  • 广度优先遍历(BFS)算法的概述、代码实现和应用
    本文介绍了广度优先遍历(BFS)算法的概述、邻接矩阵和邻接表的代码实现,并讨论了BFS在求解最短路径或最短步数问题上的应用。以LeetCode中的934.最短的桥为例,详细阐述了BFS的具体思路和代码实现。最后,推荐了一些相关的BFS算法题目供大家练习。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有