二叉搜索树(BST)

news2025/1/13 14:01:17

二叉搜索树是一种二叉树,但它对树中元素的顺序作了限制。在二叉搜索树中,对于任意一个结点,它的左子树(如果有)中的所有元素值都小于它,它的右子树中的所有元素值都大于它。那么基于这个性质,对于二叉搜索树的插入删除或是查找等操作的逻辑就非常清楚了。下面给出C语言实现的二叉搜索树代码,对于每一种操作,都有递归写法和迭代写法两种实现方法:

如果插入的数据分布比较随机,那么二叉树的树高的量级就为log_{2}N,N为结点的数量。插入时需要找到插入位置,也就是最底层为NULL的结点,一次插入的时间复杂度就为log_{2}N。 相似的,删除查找或是修改等操作都需要在树中找到相应的结点,它们的时间复杂度也就为log_{2}N。但这都是建立在输入数据随机的情况下,如果输入数据比较有序,那么建立的树高就不会是log_{2}N,一次操作需要的时间就多多了。

搜索二叉树结构定义:

//二叉搜索树,假设关键字为整型,且不存在重复的关键字
//在二叉搜索树中,每个结点的左子树(如果有)中的关键字都小于它
//每个结点右子树(如果有)中的关键字都大于它
//二叉搜索树结点定义
typedef struct BstNode {
	int val;//关键字
	struct BstNode* left;//左孩子
	struct BstNode* right;//右孩子
}BstNode;

插入:

//向二叉树中插入一个结点
//插入结点的逻辑与查找是相似的,假设要插入的结点就在树中,那么我们现在要找到该结点
//就需要使用查找,最后找到该结点应该在的位置,那么如果这个结点不存在,最后找到的位置就为空
//但这个位置刚好也是新节点要插入的位置。并且插入函数返回树或子树的根节点
BstNode* Insert(BstNode* root, int k) {

	//插入的递归写法
	//如果root为空,那么root就是要插入的位置
	if (root == NULL) {
		//建立新结点
		BstNode* p = (BstNode*)malloc(sizeof(BstNode));
		if (p == NULL) {
			perror("malloc");
			return NULL;
		}
		p->left = p->right = NULL;
		p->val = k;
		return p;
	}
	else {
		//如果根节点不为空,那么就需要判断在其左子树还是右子树中插入
		if (root->val > k)
			//使用这样的逻辑,当root的左子树为空,那么建立的新节点就会连接在其左子树
			root->left = Insert(root->left, k);
		else
			root->right = Insert(root->right, k);
		return root;
	}

	插入的迭代写法
	需要找到要插入位置的父节点
	//BstNode* father = NULL;
	//BstNode* child = root;
	//if (child == NULL) {
	//	//建立新结点
	//	child = (BstNode*)malloc(sizeof(BstNode));
	//	if (child == NULL) {
	//		perror("malloc");
	//		return NULL;
	//	}
	//	child->left = child->right = NULL;
	//	child->val = k;
	//	return child;
	//}
	//else {
	//	while (child) {
	//		if (child->val > k) {
	//			father = child;
	//			child = child->left;
	//		}
	//		else {
	//			father = child;
	//			child = child->right;
	//		}
	//	}
	//	child = (BstNode*)malloc(sizeof(BstNode));
	//	if (child == NULL) {
	//		perror("malloc");
	//		return NULL;
	//	}
	//	child->left = child->right = NULL;
	//	child->val = k;
	//	if (father->val > k)
	//		father->left = child;
	//	else
	//		father->right = child;
	//	return root;
	//}

}

删除:

//找到某一个有两个孩子的结点的中序前驱结点
BstNode* FindPre(BstNode* root) {
	BstNode* pre = root->left;
	while (pre->right)
		pre = pre->right;
	return pre;
}

//找到某一个有两个孩子的结点的中序后继
BstNode* FindNext(BstNode* root) {
	BstNode* next = root->right;
	while (next->left)
		next = next->left;
	return next;
}

//删除bst中的结点,总共分为三种情况
//1.删除的结点为叶节点,那么可以直接删除,因为删除叶节点不会影响其他结点
//2.删除的结点有一个孩子,那么只需要将这个孩子代替删除结点的位置即可
//3.删除的结点有两个孩子,那么在删除时就需要对这个两个孩子的位置进行处理
//首先找到要删除结点的前驱或是后继,然后删除前驱或后继结点,再将前驱或后继
//的关键字赋给要删除结点,也就是说,用它的前驱或后继来替换它的位置
//因为这样可以保证满足bst的结构要求,函数返回删除结点之后的根节点
BstNode* Delete(BstNode* root, int k) {
	
	递归写法
	删除首先还是找到查找到要删除的结点
	//if (root == NULL) {
	//	//root为空时,说明要删除的结点不存在
	//	return NULL;
	//}
	//else {
	//	if (root->val == k) {
	//		//要删除结点就为root
	//		if (root->left == NULL && root->right == NULL) {
	//			//叶节点的情况
	//			free(root);
	//			root = NULL;
	//		}
	//		else if (root->left == NULL || root->right == NULL) {
	//			//只有一个孩子的情况
	//			BstNode* tmp = root->left ? root->left : root->right;
	//			free(root);
	//			root = tmp;
	//		}
	//		else {
	//			//有两个孩子的情况
	//			//找到k的中序遍历前驱
	//			BstNode* pre = FindPre(root);
	//			root->val = pre->val;
	//			//然后在其左子树中删除其前驱
	//			root->left = Delete(root->left, root->val);
	//		}
	//	}
	//	else if (root->val > k)
	//		root->left = Delete(root->left, k);
	//	else
	//		root->right = Delete(root->right, k);
	//	return root;
	//}

	//迭代写法
	//首先需要找到被删除结点及其父节点
	BstNode* father = NULL;
	BstNode* child = root;
	if (child == NULL) {
		return NULL;
	}
	else {
		//首先处理删除根节点的情况
		if (root->val == k) {
			//没有孩子
			if (root->left == NULL && root->right == NULL) {
				free(root);
				return NULL;
			}
			else if (root->left == NULL || root->right == NULL) {
				BstNode* ret = root->left ? root->left : root->right;
				free(root);
				return ret;
			}
			else {
				//首先找到前驱和前驱的父节点
				BstNode* prefather = root;
				BstNode* pre = root->left;
				while (pre->right) {
					prefather = pre;
					pre = pre->right;
				}
				int val = pre->val;
				//前驱一定没有右子树
				if (prefather == root) {
					//如果前驱的父节点是root,那么需要将前驱的左子树连接在root的左子树
					prefather->left = pre->left;
					free(pre);
				}
				else {
					//否则将前驱的左子树连接在父节点的右子树
					prefather->right = pre->left;
					free(pre);
				}
				//将root的val修改
				root->val = val;
				return root;
			}
		}
		else {
			//删除的不是根节点,那么先找到删除结点和它的父节点
			while (child->val != k) {
				if (child->val > k) {
					father = child;
					child = child->left;
				}
				else {
					father = child;
					child = child->right;
				}
			}
			//删除结点分为三种情况
			//没有孩子
			if (child->left == NULL && child->right == NULL) {
				if (child->val < father->val)
					father->left = NULL;
				else
					father->right = NULL;
				free(child);
				child = NULL;
			}
			else if (child->left == NULL || child->right == NULL) {
				BstNode* ret = child->left ? child->left : child->right;
				if (child->val < father->val)
					father->left = ret;
				else
					father->right = ret;
				free(child);
				child = NULL;
			}
			else {
				//首先找到前驱和前驱的父节点
				BstNode* prefather = child;
				BstNode* pre = child->left;
				while (pre->right) {
					prefather = pre;
					pre = pre->right;
				}
				int val = pre->val;
				//前驱一定没有右子树
				if (prefather->val > pre->val)
					prefather->left = pre->left; 
				else {
					prefather->right = pre->left;
				}
				free(pre);
				pre = NULL;
				//将root的val修改
				child->val = val;
			}
			return root;
		}
	}

}

//修改某个元素的值
BstNode* ChangeNodeVal(BstNode* root, int k, int target) {
	BstNode* tmp = FindKeyNode(root, k);
	if (tmp == NULL)
		return NULL;
	tmp->val = target;
	return tmp;
}

查找:

//在bst中查找一个值为k的结点
//由bst的定义可知,对于当前子树,如果根节点的值等于k,那么直接返回根节点
//如果根节点的值大于k,那么k一定位于它的左子树,否则位于右子树
//对于每个子树都是同样的处理过程,所以可以使用递归来实现
//当发现当前子树为空时,就说明没有找到目标值
BstNode* FindKeyNode(BstNode* root, int k) {

	//递归实现
	//root为空,则查找失败
	if (root == NULL) {
		return NULL;
	}
	//root不为空,分为三个逻辑
	if (root->val == k)
		return root;
	else if (root->val > k)
		return FindKeyNode(root->left, k);
	else
		return FindKeyNode(root->right, k);

	迭代实现
	//BstNode* curnode = root;//当前判断的结点
	如果它为空,则查找失败
	//while (curnode) {
	//	if (curnode->val == k)
	//		return curnode;
	//	else if (curnode->val > k)
	//		curnode = curnode->left;
	//	else
	//		curnode = curnode->right;
	//}

	//return curnode;
}

修改:

//修改某个元素的值
BstNode* ChangeNodeVal(BstNode* root, int k, int target) {
	BstNode* tmp = FindKeyNode(root, k);
	if (tmp == NULL)
		return NULL;
	tmp->val = target;
	return tmp;
}

测试代码:

#include <stdio.h>
#include <stdlib.h>

int main() {

	//建立二叉树
	printf("请输入初始元素的个数:");
	int num;
	scanf("%d", &num);
	printf("请输入这%d个元素的值:\n", num);
	BstNode* root = NULL;
	for (int i = 0; i < num; i++) {
		int key;
		scanf("%d", &key);
		root = Insert(root, key);
	}
	printf("\n----------------------------\n");
	InOrderPrint(root);
	printf("\n----------------------------\n");
	root = Delete(root, 44);
	printf("删除后的序列是:\n");
	InOrderPrint(root);
	printf("\n-------------------------\n");
	for (int i = 0; i <= 100; i++) {
		BstNode* tmp = FindKeyNode(root, i);
		if (tmp == NULL) {
			printf("元素%d不存在\n", i);
		}
		else {
			printf("查找%d,找到的元素是%d\n", i, tmp->val);
			
			root = Delete(root, tmp->val);
			printf("删除后的序列是:\n");
			InOrderPrint(root);
			printf("\n-------------------------\n");
		}
	}

	return 0;
}

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

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

相关文章

[计算机图形学]蒙特卡洛积分与路径追踪(前瞻预习/复习回顾)

一、Monte Carlo Integration—蒙特卡洛积分 我们学过如可求解不定积分&#xff0c;前提是我们可以求出这个函数的解析式&#xff0c;但是如果我们不知道这个函数解析式是什么怎么办呢&#xff1f;我们知道黎曼积分&#xff0c;它可以把整个函数图像切分成无限密的小长方形来求…

达梦数据库中注释的使用

在管理规模较大的数据库时&#xff0c;我们往往需要面对大量的表与视图&#xff0c;与此同时在表与视图中可能会存在着许多的字段&#xff0c;让人难以迅速分辨&#xff0c;不利于对于数据库对象的管理。除了在命名时&#xff0c;对于有意义的表、视图及列&#xff0c;应尽量赋…

EPIT定时器实验(一)

EPIT定时器简介 EPIT&#xff1a;Enhanced Periodic Interrupt Timer&#xff0c;直译就是增强的周期中断定时器&#xff0c;它主要完成周期性中断定时的。 STM32里面的定时器有很多其它功能&#xff0c;比如输入捕获、PWM输出等&#xff0c;但是I.MX6U的的EPIT定时器只是完成…

c#对c++动态库的调用全流程以及详解

如果对pcl里的函数导出为动态库&#xff0c;分为以下几部分&#xff1a; 对c动态库的导出&#xff1b;c#对c动态库的加载&#xff1b;c#对第2步的调用 一、对c动态库的导出 定义导出的宏定义&#xff1a; #ifndef EXPORT # define EXPORT(rettype) __declspec( dllexport …

数据结构之单链表oJ练习

目录 1.移除单链表中与给数相同的元素 2.反转链表 3.找中间节点 4.找倒数第k个 5.合并两个有序链表 6.链表分割 7.链表的回文结构 8.找公共节点 1.移除单链表中与给数相同的元素 解题思路&#xff1a; 初始化一个新链表&#xff0c;从头结点开始遍历&#xff0c;若相同…

天猫数据分析:2023年Q1空气净化器TOP10品牌销量排行榜

随着全球工业化程度的提高&#xff0c;全球空气污染程度仍将继续增加&#xff0c;各领域对空气净化器的需求不断增长&#xff0c;这也有望带动国内空气净化器行业的市场规模保持增长。 根据鲸参谋电商数据平台的相关数据显示&#xff0c;2023年Q1在天猫平台上&#xff0c;空气净…

02_Lock锁

首先看一下JUC的重磅武器——锁&#xff08;Lock&#xff09; 相比同步锁&#xff0c;JUC包中的Lock锁的功能更加强大&#xff0c;它提供了各种各样的锁&#xff08;公平锁&#xff0c;非公平锁&#xff0c;共享锁&#xff0c;独占锁……&#xff09;&#xff0c;所以使用起来…

shell终端敲入命令计算机都做了什么?

本文参考&#xff1a; linux命令行的运行原理是什么&#xff1f; - 知乎 (zhihu.com) 8.1 键盘敲入 A 字母时&#xff0c;操作系统期间发生了什么&#xff1f; | 小林coding (xiaolincoding.com) shell命令背后的执行过程_shell 命令执行的产生的进程_kyrieguard的博客-CSDN博客…

三维动画渲染用什么软件好?

三维渲染是通过计算机应用程序把 3D 模型生成图像&#xff08;照片级真实感或非照片级真实感&#xff09;的自动化过程&#xff0c;三维动画渲染是动画制作过程的最后一步&#xff0c;该过程将各种视觉效果应用于最终模型&#xff0c;例如阴影、纹理、灯光反射和运动模糊等&…

史上最全的接口测试,吐血整理从零到接口自动化实战...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 接口测试&#xf…

04_并发容器类

1. 重现线程不安全&#xff1a;List 首先以List作为演示对象&#xff0c;创建多个线程对List接口的常用实现类ArrayList进行add操作。 public class NotSafeDemo {public static void main(String[] args) {List<String> list new ArrayList<>();for (int i 0; i…

keil移植linux(makefile)

文章目录 运行环境&#xff1a;1.1 freeRTOS_LED工程移植1)修改cubeMX配置2)setting设置3)launch设置4)修改makefile5)修改代码6)实验效果 运行环境&#xff1a; ubuntu18.04.melodic 宏基暗影骑士笔记本 stm32f427IIH6 stlink 9-24v可调电源 robomaster A 板 1.1 freeRTOS_L…

Kubernetes集群的类似top的查看指标的工具ktop

Kubernetes集群的类似top的工具。 遵循Unix/Linux顶级工具的传统&#xff0c;ktop是一个显示有关Kubernetes集群中运行的节点、pod和其他工作负载资源的有用度量信息的工具。 项目地址&#xff1a;https://github.com/vladimirvivien/ktop使用效果图&#xff1a; 特性 集群资…

Linux如何压缩和解压文件

先看压缩 Linux zip 命令用于压缩文件。 zip 是个使用广泛的压缩程序&#xff0c;压缩后的文件后缀名为 .zip 将 /home/html/ 这个目录下所有文件和文件夹打包为当前目录下的 html.zip&#xff1a; zip -r html.zip /home/html 如果在我们在 /home/html 目录下&#xff0c;可…

经济回暖、兴趣电商升级,品牌在竞争白热化的市场中如何突围?| D3大会圆桌回顾

冬去春来&#xff0c;消费市场韧性回弹&#xff0c;消费趋势正处于“转折”和“跃升”的阶段。新的机遇和挑战也将伴随着新的思维、方法和模式&#xff0c;呈现出更多元的变化和创新&#xff1a;渠道虚实融合&#xff0c;内容为王&#xff0c;社会化媒体成为主战场等消费场景不…

13-NumPy

文章目录 一.基础1.Ndarray对象2.数据类型 二.数组1.数组属性&#xff08;1&#xff09;arange&#xff08;2&#xff09;shape&#xff08;3&#xff09;ndim&#xff08;4&#xff09;itemsize 2.创建数组&#xff08;1&#xff09;empty&#xff08;2&#xff09;zero&#…

【接口自动化测试】月薪12k必会技术,从0到1学习接口自动化测试,6个操作安排的明明白白

导读&#xff1a;在所有的开发测试中&#xff0c;接口测试是必不可少的一项。有效且覆盖完整的接口测试&#xff0c;不仅能保障新功能的开发质量&#xff0c;还能让开发在修改功能逻辑的时候有回归的能力&#xff0c;同时也是能优雅地进行重构的前提。编写接口测试要遵守哪些原…

电商直播商家崛起所面临的问题,订单管理系统起关键性作用

随着短视频直播的兴起&#xff0c;电商企业再次迎来大爆发&#xff0c;随着销量的猛增&#xff0c;随之而来的的订单处理成了各大商家的头等大事&#xff0c;面对再次崛起的电商蓝库云认为企业在订单管理会经常遇到以下问题&#xff1a; 电商企业在订单管理中可能面临以下问题…

js版计算连续12个月计算不超3万公里

<!--考虑比亚迪车友不是程序员的多&#xff0c;写了个html版的&#xff0c;复制以下代码在记事本&#xff0c;改后缀名为test.html&#xff0c;然后用浏览器打开--> <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>…

基于SpringBoot3从零配置MybatisPlus

基于SpringBoot3从零配置MybatisPlus记录 文章目录 1.环境2.表数据准备3. 配置pom配置yml 配置MapperScan 3.问题总结问题1: Property sqlSessionFactory or sqlSessionTemplate are required问题2&#xff1a;org.apache.ibatis.binding.BindingException: Invalid bound stat…