二叉查找树详解

news2024/11/28 14:55:00

目录

二叉查找树的定义

二叉查找树的基本操作

查找

插入

建立

删除

二叉树查找树的性质

二叉查找树的定义

二叉查找树是一种特殊的二叉树,又称为排序二叉树、二叉搜索树、二叉排序树。

二叉树的递归定义如下:

(1)要么二叉查找树是一棵空树。

(2)要么二叉查找树由根节点、左子树、右子树组成,其中左子树和右子树都是二叉查找树,且左子树上所以结点的数据域均小于或等于根节点的数据域,右子树上的所有结点的数据域均大于根节点的数据域。

二叉查找树的基本操作

查找

进行二叉树的查找操作时,由于无法确定二叉树的具体特性,因此只能对左右子树都进行递归遍历。但是二叉查找树的性质决定了可以只选择一棵子树进行遍历。因此查找将会是从根节点查找的一条路径,故最坏时间复杂度是O\left ( h \right ),其中h是二叉查找树的高度。于是就可以得到查找操作的基本思路:

(1)如果当前根节点root为空,说明查找失败,返回。

(2)如果需要查找的x等于当前根节点的数据域root->data,查找成功,访问。

(3)如果需要查找的x小于当前根节点的数据域root->data,说明应该往左子树查找,因此向root->lchild递归。

(4)如果需要查找的x大于当前结点的数据域root->data,则应该往右子树查找,因此向root->rchild递归。

void search(node* root,int x){
	if(root==NULL){
		printf("search failed\n");
		return;
	}
	if(x==root->data){
		printf("%d\n",root->data);
	}
	else if(x<root->data){
		search(root->lchild,x);
	}
	else{
		search(root->rchild,x);
	}
}

可以看到,和普通二叉树的查找函数不同,二叉查找树的查找在于对左右子树的选择递归。在普通二叉树中,无法确定需要查找的值x到底是在左子树还是右子树,但是在二叉查找树中就可以确定,因为二叉查找树中的数据域顺序总是左子树<根节点<右子树。

插入

对一棵二叉查找树来说,查找某个数据域的结点一定是沿着确定的路径进行的。因此,当对某个需要查找的值在二叉查找树中查找成功,说明结点已经存在。反之,如果这个需要查找的值在二叉查找树中查找失败,那么说明查找失败的地方一定是结点需要插入的地方。因此可以在上面查找操作的基础上,在root==NULL时新建需要插入的点。

void insert(node* &root,int x){
	if(root==NULL){
		root=newNode[x];
		return;
	}
	if(x==root->data){
		return;
	}
	else if(x<root->data){
		insert(root->lchild,x);
	}
	else{
		insert(root->rchild,x);
	}
}

建立

node* Create(int data[],int n){
	node* root=NULL;
	for(int i=0;i<n;i++){
		insert(root,data[i]);
	}
	return root;
}

需要注意的是,即使是一组相同的数字,如果插入它们的顺序不同,最后生成的二叉查找树也可能不同。

删除

把二叉查找树中比结点权值小的最大结点称为该结点的前驱,而把比结点权值大的最小结点称为该结点的后继。显然,结点的前驱是该结点左子树的最右结点(也就是从左子树根节点开始不断沿着rchild往下直到rchild为NULL时的结点),而结点的后继则是该结点右子树的最左节点(也就是从右子树根节点开始不断沿着lchild往下直到lchild为NULL时的结点)。下面两个函数用来寻找以root为根的树中最大或最小权值的结点,用以辅助寻找结点的前驱和后继:

//寻找以root为根结点的树中的最大权值结点 
node* findMax(node* root){
	while(root->rchild!=NULL){
		root=root->rchild;
	}
	return root;
}
//寻找以root为根结点的树中的最小权值结点
node* findMin(node* root){
	while(root->lchild!=NULL){
		root=root->lchild;
	}
	return root;
} 

删除操作的基本思路如下:

(1)如果当前结点root为空,说明不存在权值为给定权值x的结点,直接返回。

(2)如果当前结点root的权值恰为给定的权值x,说明找到了想要删除的结点,此时进入删除处理。如果当前结点root不存在左右孩子,说明是叶子节点,直接删除。如果当前结点root存在左孩子,那么在左子树中寻找结点前驱pre,然后让前驱的数据覆盖root,接着在左子树中删除结点pre。如果当前结点root存在右孩子,那么在右子树中寻找结点后继next,然后让next的数据覆盖root,接着在右子树中删除结点next。

(3)如果当前结点root的权值大于给定权值的x,则在左子树中递归删除权值为x的结点。

(4)如果当前结点root的权值小于给定权值的x,则在右子树中递归删除权值为x的结点。

//寻找以root为根结点的树中的最大权值结点 
node* findMax(node* root){
	while(root->rchild!=NULL){
		root=root->rchild;
	}
	return root;
}
//寻找以root为根结点的树中的最小权值结点
node* findMin(node* root){
	while(root->lchild!=NULL){
		root=root->lchild;
	}
	return root;
} 
void deleteNode(node* &root,int x){
	if(root==NULL){
		return;
	}
	if(root->data==x){
		if(root->lchild==NULL&&root->rchild==NULL){
			root==NULL;
		}
		else if(root->lchild!=NULL){
			node* pre=findMax(root->lchild);
			root->data=pre->data;
			deleteNode(root->lchild,pre->data);
		}
		else{
			node* next=findMin(root->rchild);
			root->data=next->data;
			deleteNode(root->rchild,next->data);
		}
	}
	else if(root->data>x){
		deleteNode(root->lchild,x);
	}
	else{
		deleteNode(root->rchild,x);
	}
}

但是也要注意,总是优先删除前驱或后继容易导致树的左右子树高度极度不平衡,使得二叉查找树退化成一条链。解决这一问题的办法有两种:一种是每次交替删除前驱或后继;另一种是记录子树高度,总是优先在高度较高的一棵子树里删除结点。

二叉树查找树的性质

二叉查找树一个实用的性质:对二叉查找树进行中序遍历,遍历的结果是有序的

另外,如果合理调整二叉查找树的形态,使得树上的每个结点都尽量有两个子节点,这样整个二叉查找树的高度就会很低,也即树的高度大概在logN的级别。

例题

给出N个正整数来作为一棵二叉排序树的结点插入顺序,问:这串序列是否是该二叉排序树的先序序列或是该二叉排序树的镜像树的先序序列。所谓镜像树是指交换二叉树的所有结点的左右子树而形成的树(也即左子树所有结点数据域大于或等于根节点,而根节点数据域小于右子树所有结点的数据域)。如果是,则输出YES,并输出对应的树的后序序列;否则,输出NO。

思路

通过给定的插入序列,构建出二叉排序树。对镜像树的先序遍历只需要在原树的先序遍历时交换左右子树的访问顺序即可。

//镜像树先序遍历,结果存放于vi
void preordermirror(node* root,vector<int>&vi){
	if(root==NULL){
		return;
	}
	vi.push_back(root->data);
	preordermirror(root->right,vi);
	preordermirror(root->left,vi);
} 

注意点

使用vector来存放初始序列、先序序列、镜像树先序序列,可以方便相互之间的比较。若使用数组,则比较操作需要使用循环才能实现。

#include<cstdio>
#include<vector>
using namespace std;
struct node{
	int data;
	node* left,*right;
}; 
vector<int> origin,pre,preM,post,postM;
void insert(node* &root,int data){
	if(root==NULL){
		root=new node;
		root->data=data;
		root->left=root->right=NULL;
		return;
	}
	if(data<root->data){
		insert(root->left,data);
	}
	else{
		insert(root->right,data);
	}
}
void preorder(node* root,vector<int> &vi){
	if(root==NULL){
		return;
	}
	vi.push_back(root->data);
	preorder(root->left,vi);
	preorder(root->right,vi);
}
void preordermirror(node* root,vector<int> &vi){
	if(root==NULL){
		return;
	}
	vi.push_back(root->data);
	preordermirror(root->right,vi);
	preordermirror(root->left,vi);
}
void postorder(node* root,vector<int> &vi){
	if(root==NULL){
		return;
	}
	postorder(root->left,vi);
	postorder(root->right,vi);
	vi.push_back(root->data);
}
void postordermirror(node* root,vector<int> &vi){
	if(root==NULL){
		return;
	}
	postordermirror(root->right,vi);
	postordermirror(root->left,vi);
	vi.push_back(root->data);
}
int main(){
	int n,data;
	node* root=NULL;
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d",&data);
		origin.push_back(data);
		insert(root,data);
	}
	preorder(root,pre);
	preordermirror(root,preM);
	postorder(root,post);
	postordermirror(root,postM);
	if(origin==pre){
		printf("YES\n");
		for(int i=0;i<post.size();i++){
			printf("%d",post[i]);
			if(i<post.size()-1){
				printf(" ");
			}
		}
	}
	else if(origin==preM){
		printf("YES\n");
		for(int i=0;i<postM.size();i++){
			printf("%d",postM[i]);
			if(i<postM.size()-1){
				printf(" ");
			}
		}
	}
	else{
		printf("NO\n");
		return 0;
	}
}

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

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

相关文章

CorelDraw安装时界面显示不全的解决方案

问题原因&#xff1a;安装包权限 解决方案&#xff1a; 1、安装包解压后&#xff0c;找到Setup文件&#xff0c;复制粘贴到当前文件夹并重命名为Getup后&#xff0c;右击Getup文件&#xff0c;选择“以管理员身份运行” 说明&#xff1a;除了命名Gsetup。还可以命名为其他的…

【Java】Java18的新特性

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

史上最有趣嫁妆:晋公盘的传奇

在遥远的春秋时代&#xff0c;晋国的晋文公为他的女儿用心打造了一件独特的嫁妆——晋公盘。 晋公盘由青铜制成&#xff0c;形状独特&#xff0c;工艺精湛。在晋公盘内底中央&#xff0c;一对精美的浮雕龙盘绕成圆形&#xff0c;盘上饰有鸟、龟、鱼、蛙等多种动物&#xff0c;最…

带你学习Mybatis之逆向工程

逆向工程 可以针对单表自动生成MyBatis执行所需要的代码&#xff0c;包括&#xff1a;Mapper.java&#xff0c;Mapper.xml&#xff0c;实体类&#xff0c;这样可以减少重复代码的编写 <dependency> <groupId>org.mybatis.generator</groupId> …

【数据结构】初识数据结构之复杂度与链表

【数据结构】初识数据结构之复杂度与链表 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C语言学习之路 文章目录 【数据结构】初识数据结构之复杂度与链表前言一.数据结构和算法1.1数据结构1.2算法1.3数据结构和算法的重要性 二.时间与空间…

人工智能对话系统源码 手机版+电脑版二合一 全端支持 前后端分离 带完整的安装代码包以及搭建部署教程

系统概述 该系统采用前后端分离的设计模式&#xff0c;前端负责用户界面展示与交互&#xff0c;后端负责数据处理与业务逻辑实现。前后端通过API接口进行通信&#xff0c;实现数据的实时传输与处理。系统支持全端访问&#xff0c;无论是手机还是电脑&#xff0c;都能获得良好的…

Type-C接口,乱成一锅粥了!

前言 小白第一次接触Type-C接口的时候是2017年的夏天&#xff0c;那时候小白买了小米最新发布的小米5x手机&#xff0c;使用的是相当主流的Type-C接口。 在2017年之前&#xff0c;很多手机还是使用Micro-USB作为手机首选的充电接口。 当同宿舍的小伙伴还在为给手机充电需要分辨…

Linux卸载RocketMQ教程【带图文命令巨详细】

巨详细Linux卸载RocketMQ教程 #查询rocketmq进程 ps -ef | grep rocketmq #杀掉相关进程 kill -9 进程id #查找安装目录 find / -name runbroker.sh #删除rocketMQ目录 rm -rf 安装目录框起来的就是进程id&#xff0c;全部杀掉 这里就是我的安装目录&#xff0c;我的删除命令…

基于STM32开发的智能语音控制系统

目录 引言环境准备智能语音控制系统基础代码实现&#xff1a;实现智能语音控制系统 4.1 语音识别模块数据读取4.2 设备控制4.3 实时数据监控与处理4.4 用户界面与反馈显示应用场景&#xff1a;语音控制的家居设备管理问题解决方案与优化收尾与总结 1. 引言 随着人工智能技术…

从零开始实现自己的串口调试助手(10) - 优化 收尾 + 打包

光标位置优化 在接收槽函数中更新光标位置: // 让光标始终在结尾 ui->textEditRev->moveCursor(QTextCursor::End); ui->textEditRev->ensureCursorVisible(); // 让光标可视化 //记得HEX显示槽函数底下也得加上这两行代码 新的接收槽函数如下: void Wid…

10秒钟docker 安装Acunetix

1、拉取镜像&#xff1a; 2、查看镜像&#xff1a; [rootdns-server ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE quay.io/hiepnv/acunetix latest f8415551b8f4 2 months ago 1.98GB 3、运行镜像&#xff1a; …

算法训练营day03--203.移除链表元素+707.设计链表+206.反转链表

一、203.移除链表元素 题目链接&#xff1a;https://leetcode.cn/problems/remove-linked-list-elements/ 文章讲解&#xff1a;https://programmercarl.com/0203.%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.html 视频讲解&#xff1a;https://www.bilibili.com…

CentOS7 MySQL5.7.35主从 不停机搭建 以及配置

如需安装MySQL&#xff0c;参照MySQL 5.7.35 安装教程 https://blog.csdn.net/CsethCRM/article/details/119418841一、主&从 环境信息准备 1.1.查看硬盘信息&#xff0c;确保磁盘够用&#xff08;主&从&#xff09; df -h1.2.查看内存信息 &#xff08;主&从&am…

11 IP协议 - IP协议头部

什么是 IP 协议 IP&#xff08;Internet Protocol&#xff09;是一种网络通信协议&#xff0c;它是互联网的核心协议之一&#xff0c;负责在计算机网络中路由数据包&#xff0c;使数据能够在不同设备之间进行有效的传输。IP协议的主要作用包括寻址、分组、路由和转发数据包&am…

Android 应用权限

文章目录 权限声明uses-permissionpermissionpermission-grouppermission-tree其他uses-feature 权限配置 权限声明 Android权限在AndroidManifest.xml中声明&#xff0c;<permission>、 <permission-group> 、<permission-tree> 和<uses-permission>…

shell编程(二)——字符串与数组

本文为shell 编程的第二篇&#xff0c;介绍shell中的字符串和数组相关内容。 一、字符串 shell 字符串可以用单引号 ‘’&#xff0c;也可以用双引号 “”&#xff0c;也可以不用引号。 单引号的特点 单引号里不识别变量单引号里不能出现单独的单引号&#xff08;使用转义符…

[职场] 缺点范文 #知识分享#经验分享#媒体

缺点范文 回答示范1&#xff1a; 我的公开演讲能力比较差&#xff0c;在公共场合讲话的时候我会感到紧张&#xff0c;不过谈论我熟悉的领域我会比较放松。所以当我需要做公开发言的时候&#xff0c;我必须要准备得很充分。我确实羡慕那些无论什么话题都能够高谈阔论的人。 回…

光伏电站绘制软件的基本方法

随着可再生能源的快速发展&#xff0c;光伏电站的建设日益受到重视。为了提高光伏电站设计的效率和准确性&#xff0c;光伏电站绘制软件的应用变得至关重要。本文将介绍光伏电站绘制软件的基本方法&#xff0c;包括绘制屋顶、屋脊、障碍物和参照物&#xff0c;铺设光伏板&#…

Java Web学习笔记24——Vue项目开发流程

import是引入文件。 export是将对象导出为模块。 new Vue({ router, router: h > h(App) }).$mount(#app) App.vue: vue的组成文件以.vue结尾&#xff0c;每个组件由三个部分组成&#xff1a;<template>、<script>、<style>。 <template><d…

玩游戏时服务器遭到DDOS攻击崩溃,DDOS攻击是什么?

某网游服务器突然崩溃&#xff0c;大量玩家被迫下线。随后该游戏官方出面解释是因服务器遭遇了DDoS攻击&#xff0c;所以导致登录异常。这个DDoS攻击到底是哪里来的鬼怪&#xff0c;敢这么给自己加戏&#xff1f; 什么是DDoS攻击&#xff1f; DDoS攻击&#xff0c;专业表述是“…