数据结构:二叉排序树

什么是二叉排序树?

二叉排序树要么是空二叉树,要么具有如下特点:

  • 二叉排序树中,如果其根结点有左子树,那么左子树上所有结点的值都小于根结点的值;
  • 二叉排序树中,如果其根结点有右子树,那么右子树上所有结点的值都大小根结点的值;
  • 二叉排序树的左右子树也要求都是二叉排序树;

如图所示,就是一个二叉排序树:

在这里插入图片描述

使用二叉排序树查找关键字

二叉排序树中查找某关键字时,查找过程类似于次优二叉树,在二叉排序树不为空树的前提下,首先将被查找值同树的根结点进行比较,会有3种不同的结果:

  • 如果相等,查找成功;
  • 如果比较结果为根结点的关键字值较大,则说明该关键字可能存在其左子树中;
  • 如果比较结果为根结点的关键字值较小,则说明该关键字可能存在其右子树中;

实现函数为:(运用递归的方法)

BSTree SreachBST(BSTree T, int key){//如果递归过程中 T 为空,则查找结果,返回NULL;或者查找成功,返回指向该关键字的指针if ((!T) || (key == T->data)) return T;else if(key < T->data) return SreachBST(T->lchild, key);//递归遍历其左孩子else return SreachBST(T->rchild, key);//递归遍历其右孩子
}

二叉排序树中插入关键字

二叉排序树本身是动态查找表的一种表示形式,有时会在查找过程中插入或者删除表中元素,当因为查找失败而需要插入数据元素时,该数据元素的插入位置一定位于二叉排序树的叶子结点,并且一定是查找失败时访问的最后一个结点的左孩子或者右孩子。

例如,在图 1 的二叉排序树中做查找关键字 1 的操作,当查找到关键字 3 所在的叶子结点时,判断出表中没有该关键字,此时关键字 1 的插入位置为关键字 3 的左孩子。

所以,二叉排序树表示动态查找表做插入操作,只需要稍微更改一下上面的代码就可以实现,具体实现代码为:

void InserBST(BSTree &T, int key){if(!T){//如果递归过程中T为空,则初始化插入结点BSTNode *S = new BSTNode;S->data = key;S->lchild = NULL;S->rchild = NULL;T = S;}else if(key < T->data) InserBST(T->lchild, key);else if(key > T->data) InserBST(T->rchild, key);else if(key == T->data) {cout << "该关键字在已存在!!!";return;}
}

通过使用二叉排序树对动态查找表做查找和插入的操作,同时在中序遍历二叉排序树时,可以得到有关所有关键字的一个有序的序列。

例如,假设原二叉排序树为空树,在对动态查找表 {3,5,7,2,1} 做查找以及插入操作时,可以构建出一个含有表中所有关键字的二叉排序树,过程如图 2 所示:

在这里插入图片描述

图 2 二叉排序树插入过程

通过不断的查找和插入操作,最终构建的二叉排序树如图 2(5) 所示。当使用中序遍历算法遍历二叉排序树时,得到的序列为: 1 2 3 5 7 ,为有序序列。

一个无序序列可以通过构建一棵二叉排序树,从而变成一个有序序列。

二叉排序树中删除关键字

在查找过程中,如果在使用二叉排序树表示的动态查找表中删除某个数据元素时,需要在成功删除该结点的同时,依旧使这棵树为二叉排序树。

假设要删除的为结点 p ,则对于二叉排序树来说,需要根据结点 p 所在不同的位置作不同的操作,有以下 3 种可能:

1、结点 p 为叶子结点,此时只需要删除该结点,并修改其双亲结点的指针即可;

2、结点 p 只有左子树或者只有右子树,如果 p 是其双亲节点的左孩子,则直接将 p 节点的左子树或右子树作为其双亲节点的左子树;反之也是如此,如果 p 是其双亲节点的右孩子,则直接将 p 节点的左子树或右子树作为其双亲节点的右子树;

3、结点 p 左右子树都有,此时有两种处理方式:

1)令结点 p 的左子树为其双亲结点的左子树;结点 p 的右子树为其自身直接前驱结点的右子树,简单理解:因为p的左子树所有结点都小于p的右子树结点,所以只需将p的左子树重接在p的父结点的左子树上,然后将p的右子树整体接入p左子树的最右端s即可。如图 3 所示;

在这里插入图片描述

图 3 二叉排序树中删除结点(1)

2)用结点 p 的直接前驱(或直接后继)来代替结点 p ,同时在二叉排序树中对其直接前驱(或直接后继)做删除操作,简单理解:因为p的左子树的最右端s是p的左子树所有结点关键字中最大的,所以只需用其代替p,然后将s的左子树为其父结点的右子树即可。如图 4 为使用直接前驱代替结点 p :

在这里插入图片描述

图 4 二叉排序树中删除结点(2)

图 4 中,在对左图进行中序遍历时,得到的结点 p 的直接前驱结点为结点 s,所以直接用结点 s 覆盖结点 p,由于结点 s 还有左孩子,根据第 2 条规则,直接将其变为双亲结点的右孩子。

具体实现代码:(可运行)

typedef struct BSTNode{int data;struct BSTNode *lchild, *rchild;
}BSTNode, *BSTree;BSTree SreachBST(BSTree T, int key){//如果递归过程中 T 为空,则查找结果,返回NULL;或者查找成功,返回指向该关键字的指针if ((!T) || (key == T->data)) return T;else if(key < T->data) return SreachBST(T->lchild, key);//递归遍历其左孩子else return SreachBST(T->rchild, key);//递归遍历其右孩子
}
void InserBST(BSTree &T, int key){if(!T){//如果递归过程中T为空,则初始化插入结点BSTNode *S = new BSTNode;S->data = key;S->lchild = NULL;S->rchild = NULL;T = S;}else if(key < T->data) InserBST(T->lchild, key);else if(key > T->data) InserBST(T->rchild, key);else if(key == T->data) {cout << "该关键字在已存在!!!";return;}
}
void CreatBST(BSTree &T){T = NULL;int key;cin >> key;while(key != -1){//-1表示输入结束InserBST(T, key);cin >> key;}
}
void DeleteBST(BSTree &T, int key){BSTree p = T, f = NULL;while (p){//找到要删除的关键字if (p->data == key) break;f = p;//f为p的父节点if (p->data > key) p = p->lchild;else p = p->rchild;}if (!p) return; //若p为空,则表示未找到要删除的结点BSTree q = p;if ((p->lchild) && (p->rchild)){//被删除的结点左右子树均不为空BSTree s = p->lchild;while (s->rchild){//在p的左子树中找到其前驱结点,即最右下结点q = s;s = s->rchild;}p->data = s->data;if(q != p) q->rchild = s->lchild;//接入q的右子树else q->lchild = s->lchild;//接入q的左子树delete s;return;}else if (!p->rchild){//被删除结点p没有右子树,只需重接其左子树p = p->lchild;}else if (!p->lchild){//被删除结点p灭有左子树,只需重接其右子树p = p->rchild;}if (!f) T = p;//被删除的是更节电else if (q = f->lchild) f->lchild = p;//q在那边就将p重接到那边else f->rchild = p;delete q;
}

总结

使用二叉排序树在查找表中做查找操作的时间复杂度同建立的二叉树本身的结构有关。即使查找表中各数据元素完全相同,但是不同的排列顺序,构建出的二叉排序树大不相同。

例如:查找表 {45,24,53,12,37,93} 和表 {12,24,37,45,53,93} 各自构建的二叉排序树图下图所示:

在这里插入图片描述

图 5 不同构造的二叉排序树

使用二叉排序树实现动态查找操作的过程,实际上就是从二叉排序树的根结点到查找元素结点的过程,所以时间复杂度同被查找元素所在的树的深度(层次数)有关。

为了弥补二叉排序树构造时产生如图 5 右侧所示的影响算法效率的因素,需要对二叉排序树做“平衡化”处理,使其成为一棵平衡二叉树。 平衡二叉树是动态查找表的另一种实现方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/104369.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑重做系统---win10

电脑重做系统---win10 前言制作启动U盘材料方法打开网址下载启动盘制作工具参照官方说明进行制作使用U盘重做系统 常用软件官网地址 前言 记得最早学习装电脑还是04年左右&#xff0c;最为一个啥也不知道的大一傻白胖&#xff0c;花了几百大洋在电脑版把了个“电脑组装与维修”…

一体化运维监控:提供全方位数据洞察

摘要&#xff1a;运维监控在数字化日益深入的今天变得至关重要。一体化运维监控管理平台脱颖而出&#xff0c;成为业界知名的数据采集与运维洞察方案提供商&#xff0c;为企业带来“看到一切”的全新体验。 正文&#xff1a; 在大数据时代的洪流中&#xff0c;企业不仅要面对数…

淘宝店铺商品评论数据采集,淘宝商品评论数据接口,淘宝API接口

采集淘宝店铺商品评论数据的方法如下&#xff1a; 进入主界面&#xff0c;选择"自定义任务"。将商品信息页的网址复制粘贴到网站输入框中&#xff0c;点击"保存设置"。将页面下拉到底部&#xff0c;点击"下一页"按钮&#xff0c;在右侧的操作提…

3. Windows下C++/MFC调用hiredis库操作redis示例

一、头文件目录 将之前下载和编译好的Redis目录拷贝到新建好的工程目录下面&#xff0c;再点击测试工程的右键/属性&#xff0c;点击C/常规&#xff0c;附加包含目录添加以下路径&#xff0c;注意如果原先有多个路径&#xff0c;在末尾处添加分号后再粘贴&#xff1a; 点击C/常…

在CentOS操作系统上使用yum安装/使用/卸载Docker容器引擎

前言 简单记录一下如何在CentOS操作系统上安装、使用、卸载Docker容器引擎&#xff0c;以及配置Docker镜像加速地址。 一、在CentOS操作系统上安装Docker容器引擎 &#xff08;1&#xff09;首先更新yum软件包索引 yum check-update&#xff08;2&#xff09;安装yum软件包管…

vue引入jQuery

配置 下载 npm install jquery --save在build的webpack.base.conf中 var webpackrequire("webpack")在module.exports中: plugins: [ //   new webpack.optimize.CommonsChunkPlugin(common.js),new webpack.ProvidePlugin({jQuery: "jquery",$: &quo…

chapter8 Dimensionality Reduction(降维)

设置 首先&#xff0c;确保代码在python2和python3中都能正常工作&#xff0c;导入一些通用模块&#xff0c;确保MatplotLib以内联方式绘制图形&#xff0c;并准备一个函数来保存这些图形: from __future__ import division,print_function,unicode_literalsimport numpy as …

微服务13-Seata的四种分布式事务模式

文章目录 XA模式实现XA模式 AT模式AT模式的脏写问题&#xff08;对同数据并发写的问题&#xff09;其他事务不获取全局锁的一个情况&#xff08;AT模式写隔离的实现&#xff09;实现AT模式 TCC模式TCC实现我们怎么样去判断是否空回滚和业务悬挂&#xff1f;业务分析 Saga模式总…

【学习笔记】项目进行过程中遇到有关composer的问题

composer.json内容详解 以项目中的composer.json为例&#xff0c;参考文档。 name&#xff1a;composer包名type&#xff1a;包的类型&#xff0c;project和library两种keywords&#xff1a;关键词&#xff0c;方便别人在安装时通过关键词检索&#xff08;没试过&#xff0c;好…

Java 中 Volatile 关键字

基本概念 补充一下 java 内存模型中的 可见性、原子性和有序性 可见性&#xff1a; 指的是线程之间的可见性&#xff0c;一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果&#xff0c;另一个线程马上可以看到。比如 &#xff1a;用 volatile 修饰的变量&am…

froeach迭代删除和List迭代删除问题

场景:我有一个 List<ISSLogMessage> records 数据,需要从里面删除指定内容数据 第一次写成 foreach(var item in records) {if (item.logMessage.Contains("上传通行记录"))records.Remove(item); } 直接报错,因为foreach 是个迭代器 直接移除它的对象会报…

Redis实现简易消息队列的三种方式

Redis实现简易消息队列的三种方式 消息队列简介 消息队列是一种用于在计算机系统中传递和处理数据的重要工具。如果你完全不了解消息队列&#xff0c;不用担心&#xff0c;我将尽力以简单明了的方式来解释它。 首先&#xff0c;想象一下你正在玩一个游戏&#xff0c;而游戏中…

Web应用防火墙的性能优化技术

Web应用防火墙&#xff08;WAF&#xff09;是企业网络安全的重要屏障&#xff0c;其性能直接影响到网络服务的质量和安全。本文详细探讨了WAF性能优化的几种技术&#xff0c;旨在为网络安全专业人员提供实用的参考。 规则优化 1.1 精简规则集 规则评估&#xff1a;定期评估规…

JQuery、JSON、AJAX、XML、IO流、多线程、反射核心知识点详解

JQuery 一、什么是JQuery JQuery是JavaScript的一个框架&#xff0c;对js的封装&#xff0c;使得js简单易学 优点&#xff1a; 1、不用考虑浏览器兼容性问题 2、jquery拥有强大的选择器&#xff0c;简化了js代码 3、jquery提供了很多系统函数&#xff0c;直接调用 二、版本 1.x…

面试题补充

1.公司有几套环境&#xff1a;测试环境&#xff08;测试人员使用&#xff09;&#xff0c;开发环境&#xff08;开发人员使用&#xff09;&#xff0c;预生产环境&#xff08;测试人员使用&#xff09;&#xff0c;生产环境&#xff08;用户使用&#xff09; 2.作为一名测试&a…

C语言第一节--从c语言源码到可执行文件

C语言第一节–从c语言源码到可执行文件 编译 C 语言代码生成可执行文件的流程一般可以分为四个步骤&#xff1a;预处理、编译、汇编和链接。 假设有一个名为 “hello.c” 的 C 语言源文件&#xff0c;内容如下&#xff1a; #include <stdio.h>int main() {printf("…

Kubernetes 常用命令 持续更新

1、进入指定namespace pod kubectl exec -it --namespacekube-system g-lsb-proxy-nginx-r7zfl-2522744936-11rld /bin/sh kubectl exec -it g-lsb-proxy-nginx-r7zfl-2522744936-9tz5k -n kube-system /bin/bash2、查看k8s pod详情 kubectl describe pods -n jiankunking …

【计算机网络】网络编程接口 Socket API 解读(11)

Socket 是网络协议栈暴露给编程人员的 API&#xff0c;相比复杂的计算机网络协议&#xff0c;API 对关键操作和配置数据进行了抽象&#xff0c;简化了程序编程。 本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍&#xff0c;从而更好的理解 socket 编程。…

opencv跨平台arm交叉编译之ubuntu

目录 1. 安装交叉编译工具链2. 安装依赖3. 配置工具链3.1 新建build目录3.2 安装cmake-gui3.3 工具链配置界面进行配置3.3.1 终端输入以下命令3.3.2 点击Configure&#xff0c;弹出编译方式选择对话框&#xff1a;3.3.3 点击Next3.3.4 点击Finish3.3.5 点击Configure。3.3.6 Ge…

RISC-V 特权级架构

特权级别 级别的数值越大&#xff0c;特权级越高&#xff0c;掌控硬件的能力越强&#xff0c;在CPU硬件层面&#xff0c;M模式必须存在&#xff0c;其它模式可以不存在 执行环境调用 ecall &#xff0c;这是一种很特殊的陷入类的指令&#xff0c; 相邻两特权级软件之间的接口正…