二叉搜索树(来学包会) C++经验+1

news2024/9/25 19:01:03

目录

什么是二叉搜索树

解二叉搜索树

二叉搜索树的操作

二叉搜索树的插入(三步走)

二叉搜索树的搜索

二叉搜索树的删除

1.删除的节点是叶子节点 

2.删除的节点只有一边的子树

3.删除的节点左子树和右子树都有

详细完整代码


什么是二叉搜索树

二叉树都知道,二叉搜索树就是:每个节点的左子树的值全都小于当前节点的值,右子树的值全都大于当前节点的值。

要这个树有什么用?

1.二叉搜索树的中序遍历是有序的。

2.顾名思义就是为了搜索,线性遍历搜索一个值是全部走一遍,而对于二叉搜索树而言,每走一次就能排除一半的结果。所以就是快。当然也会出现一些特例,如下图所示

如果一开始插入的数比后面的数都大,那就变成了一串的。如果这样来搜索,和线性遍历也没区别了,所以后面开发出了AVL树和红黑树解决。

注意:二叉搜索树的值是不能被更改的!!!!(因为更改就会破坏当前的结构),比如本来2的节点你改为10,那结构就被破坏了。

解二叉搜索树

二叉搜索树是怎么插入的。插入节点后,节点的位置就不会改变了,新来的节点往下延升。

和二叉树一样就是一个递归的过程:

1.递归时判断当前节点的数值和新加入节点的数值。比较后决定向那边递归,直到找到一个空位置,这时节点就和前一个节点连接即可。

2.二叉搜索树的中序遍历是有序的。1 2 3 4 5 6 7 8 9

二叉搜索树的操作

二叉搜索树的插入(三步走)

void insert(int key)
{
	//定义一个临时指针 用于移动
	Node* temp = root;//方便移动 以及 跳出循环
	Node* prev = NULL;//定位到待插入位置的前一个结点
    //循环的比较直到空为止
	while (temp != NULL)
	{
		prev = temp;
		if (key < temp->data)
		{
			temp = temp->left;
		}
		else if(key > temp->data)
		{
			temp = temp->right;
		}
		else
		{
			return;
		}
	}
    //这一步创造节点,并且将节点与前一个节点连接。
	if (key < prev->data)
	{
		prev->left = (Node*)malloc(sizeof(Node));
		prev->left->data = key;
		prev->left->left = NULL;
		prev->left->right = NULL;
	}
	else
	{
		prev->right = (Node*)malloc(sizeof(Node));
		prev->right->data = key;
		prev->right->left = NULL;
		prev->right->right = NULL;
	}
}

 第一步:定义变量,变量tmp 用来找位置 变量prev 用来记录前面一个节点(方便新节点)

 第二步:循环(递归)找位置,个子高的和个子矮的要分边站才整齐。

 第三步:根据传入的值创造新节点并与前一个节点连接。

二叉搜索树的搜索

/*查找元素key*/
bool search(Node* root, int key)
{
    while (root != NULL)
    {
        if (key == root->data)
            return true;
        else if (key < root->data)
            root = root->left;
        else
            root = root->right;
    }
    return false;
}

代码很简单,判断大小,循环向下,找到位置 

描述一遍找1 的过程。

我们进来先看到的5,然后1 比 5小,所以走左边,1比4小,走左边,1比2小,走左边。

完了,完全没难度的。 

总结一下:虽然这里和线性遍历相比只是少走了一次3.但是这只是在这么短的一棵树上,如果这个树特别深,每次少走一个分支,就等于少走了一半

二叉搜索树的删除

这里分三种情况:1.删除的节点是叶子节点 2.删除的节点只有一边 3.删除的节点左右都有

1.删除的节点是叶子节点 

这个没啥好说的,直接删除就行了。单身的结果

2.删除的节点只有一边的子树

 这种情况如果你是父节点的右子树,你的子节点一定都是大于你的父节点的。此时只有一边的子树。直接让子节点把你换掉即可,因为你的子树已经保证了,它是遵循二叉搜索树规则的。所以你走掉后,子节点顶替上就好了。

3.删除的节点左子树和右子树都有

前面说二叉搜索树的中序遍历是有序的。那我们删除一个节点后是不能影响其有序性的。

如图我们要删除节点5,我们此时的顺序是  

如果要在不影响有序的前提下,5的位置,可以给4 或者 给6.此时就可以得出结论了。可以让4作为根节点或者让6作为根节点。(这个选择可以根据自己喜好来设计)

怎么得到能找到4 和 6其中一个呢?

很简单,我们的4 节点其实就是左子树中最大的(左子树的最右节点)。6节点就是右子树中最小的(右子树的最左节点)。

接下来就是遍历的过程了:如果要找左子树最大的,我们就从左孩子开始,而不是从本节点开始,(因为我们是要找左子树中最大的节点)然后就一直遍历右子树知道为空,为空说明上次遍历的节点就是左子树中最大的

int delete_node(Node* node, int key)
{
	if (node == NULL)
	{
		return -1;
	}
	else
	{
		if (node->data == key)
		{
			//当我执行删除操作 需要先定位到删除结点的前一个结点(父节点)
			Node* tempNode = prev_node(root, node, key);
			Node* temp = NULL;
			
			//如果右子树为空,只需要重新连接结点(包含叶子结点),直接删除
			if (node->right == NULL)
			{
				temp = node;
				node = node->left;
				/*判断待删除结点是前一个结点的左边还是右边*/
				if (tempNode->left->data == temp->data)
				{
					Node* free_node = temp;
					tempNode->left = node;
					free(free_node);
					free_node = NULL;
				}
				else
				{
					Node* free_node = temp;
					tempNode->right = node;
					free(free_node);
					free_node = NULL;
				}
			}
			else if (node->left == NULL)
			{
				temp = node;
				node = node->right;
				if (tempNode->left->data == temp->data)
				{
					Node* free_node = temp;
					tempNode->left = node;
					free(free_node);
					free_node = NULL;
				}
				else
				{
					Node* free_node = temp;/
					tempNode->right = node;
					free(free_node);
					free_node = NULL;
				}
			}
			else//左右子树都不为空
			{
				temp = node;
				/*往左子树 找最大值*/
				Node* left_max = node;//找最大值的临时指针
				left_max = left_max->left;//先到左孩子结点
				while (left_max->right != NULL) 
				{
					temp = left_max;
					left_max = left_max->right;
				}
				node->data = left_max->data;
				if (temp != node)
				{
					temp->right = left_max->left;
					free(left_max);
					left_max = NULL;
				}
				else
				{
					temp->left = left_max->left;
					free(left_max);
					left_max = NULL;
				}
			}
			
		}
		else if(key < node->data)
		{
			delete_node(node->left, key);
		}
		else if (key > node->data)
		{
			delete_node(node->right, key);
		}
	}
}

 代码解析:

1.如果这棵树是存在的,我们先递归查找要删除的节点(比大小)。

2.找到节点后判断是上述3种的那种情况。根据情况写出代码。

 总结:单身汉没人继承,只有一个孩子独生子继承,有两个孩子找最亲近的继承。

详细完整代码

#include<stdio.h>
#include<stdlib.h>
typedef struct SortTree {
	int data;//存放数据的数据域
	struct SortTree* left;//指针域 左指针
	struct SortTree* right;//指针域 右指针
}Node;
/*全局变量*/
Node* root;//根节点
 
void Init(int);//初始化操作
void insert(int);//插入操作
void show(Node*);
int delete_node(Node*, int);
Node* prev_node(Node*, Node*, int);
bool search(Node* root, int key);
int main()
{
	Init(8);
	insert(4);
	insert(2);
	insert(5);
	insert(10);
	insert(9);
	insert(13);
	show(root);
	delete_node(root, 8);
	delete_node(root, 13);
	printf("\n");
	show(root);
}
 
/*初始化根节点
int key : 根节点的值
*/
void Init(int key)
{
	root = (Node*)malloc(sizeof(Node));
	root->data = key;
	root->left = NULL;
	root->right = NULL;
}
 
void insert(int key)
{
	//定义一个临时指针 用于移动
	Node* temp = root;//方便移动 以及 跳出循环
	Node* prev = NULL;//定位到待插入位置的前一个结点
	while (temp != NULL)
	{
		prev = temp;
		if (key < temp->data)
		{
			temp = temp->left;
		}
		else if(key > temp->data)
		{
			temp = temp->right;
		}
		else
		{
			return;
		}
	}
 
	if (key < prev->data)
	{
		prev->left = (Node*)malloc(sizeof(Node));
		prev->left->data = key;
		prev->left->left = NULL;
		prev->left->right = NULL;
	}
	else
	{
		prev->right = (Node*)malloc(sizeof(Node));
		prev->right->data = key;
		prev->right->left = NULL;
		prev->right->right = NULL;
	}
}
 
void show(Node* root)
{
	if (root == NULL)
	{
		return;
	}
	show(root->left);
	printf("%d ", root->data);
	show(root->right);
}
/*查找元素key*/
bool search(Node* root, int key)
{
	while (root != NULL)
	{
		if (key == root->data)
			return true;
		else if (key < root->data)
			root = root->left;
		else
			root = root->right;
	}
	return false;
}
int delete_node(Node* node, int key)
{
	if (node == NULL)
	{
		return -1;
	}
	else
	{
		if (node->data == key)
		{
			//当我执行删除操作 需要先定位到前一个结点
			Node* tempNode = prev_node(root, node, key);
			Node* temp = NULL;
			/*
			如果右子树为空 只需要重新连接结点
			叶子的情况也包含进去了 直接删除
			*/
			if (node->right == NULL)
			{
				temp = node;
				node = node->left;
				/*为了判断 待删除结点是前一个结点的左边还是右边*/
				if (tempNode->left->data == temp->data)
				{
					Node* free_node = temp;//释放用的指针
					tempNode->left = node;
					free(free_node);
					free_node = NULL;
				}
				else
				{
					Node* free_node = temp;//释放用的指针
					tempNode->right = node;
					free(free_node);
					free_node = NULL;
				}
			}
			else if (node->left == NULL)
			{
				temp = node;
				node = node->right;
				if (tempNode->left->data == temp->data)
				{
					Node* free_node = temp;//释放用的指针
					tempNode->left = node;
					free(free_node);
					free_node = NULL;
				}
				else
				{
					Node* free_node = temp;//释放用的指针
					tempNode->right = node;
					free(free_node);
					free_node = NULL;
				}
			}
			else//左右子树都不为空
			{
				temp = node;
				/*往左子树 找最大值*/
				Node* left_max = node;//找最大值的临时指针
				left_max = left_max->left;//先到左孩子结点
				while (left_max->right != NULL) 
				{
					temp = left_max;
					left_max = left_max->right;
				}
				node->data = left_max->data;
				if (temp != node)
				{
					temp->right = left_max->left;
					free(left_max);
					left_max = NULL;
				}
				else
				{
					temp->left = left_max->left;
					free(left_max);
					left_max = NULL;
				}
			}
			
		}
		else if(key < node->data)
		{
			delete_node(node->left, key);
		}
		else if (key > node->data)
		{
			delete_node(node->right, key);
		}
	}
}
/*定位到待删除节点的前一个结点
Node* root 从根节点开始
Node* node 待删除的结点
int key 待删除数据
*/
Node* prev_node(Node* root, Node* node, int key)
{
	if (root == NULL || node == root)
	{
		return node;
	}
	else
	{
		if (root->left != NULL && root->left->data == key)
		{
			return root;
		}
		else if(root->right != NULL && root->right->data == key)
		{
			return root;
		}
		else if (key < root->data)
		{
			return prev_node(root->left, node, key);
		}
		else
		{
			return prev_node(root->right, node, key);
		}
	}
}

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

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

相关文章

MT76X8、MT7621、MT7981和QCA9531的GPIO列表

一、 MT76X8 GPIO列表; 二、 MT7621 GPIO列表; 三、MTK7981 GPIO列表; 四、QCA9531 GPIO列表;

CentOS 7 aarch64制作openssh 9.9p1 rpm包 —— 筑梦之路

本篇文章还是基于开源项目openssh-rpms制作。 https://github.com/boypt/openssh-rpms.git 官方发行说明&#xff1a; OpenSSH: Release Notes 1. 修改version.env 2. 下载源码包 openssl网站改版&#xff0c;下载地址和之前不一样了 # 下载openssl1.1.1w源码包cd downlo…

nacos 快速入门

目录 什么是 Nacos Nacos 的主要特点&#xff1a; Dockerfiledocker-compose.yml 快速搭建 nacos 单机 什么是 Nacos Nacos/nɑ:kəʊs/ 是“动态命名和配置服务”的缩写&#xff0c;是一个用于构建云原生应用的易于使用的动态服务发现、配置和服务管理平台。 Nacos 致力于…

【JAVA开源】基于Vue和SpringBoot的图书馆管理系统

本文项目编号 T 044 &#xff0c;文末自助获取源码 \color{red}{T044&#xff0c;文末自助获取源码} T044&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析5.4 用例设计 六、核…

Linux·进程概念(上)

1.操作系统 任何计算机系统都包含一个基本的程序合集&#xff0c;称为操作系统(Operator System)。笼统的理解&#xff0c;操作系统包括&#xff1a; 内核(进程管理&#xff0c;内存管理&#xff0c;文件管理&#xff0c;驱动管理) 其他程序(函数库&#xff0c;shell程序) OS的…

知乎知+推广怎么做?投放费用是多少?

知乎以其独特的问答形式不仅吸引了大量高质量的用户群体&#xff0c;也成为了一个不可多得的品牌营销阵地。为了帮助企业更好地利用这一平台进行品牌推广&#xff0c;知乎推出了“知”推广服务&#xff0c;而作为专业的数字营销解决方案提供商&#xff0c;云衔科技更是全面支持…

Linux开发环境配置(上)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

前端文件上传全过程

特别说明&#xff1a;ui框架使用的是蚂蚁的antd 这里主要是学习前端上传接口的传递参数包括前端上传之前对于代码的整理 一、第一步将前端页面画出来 源代码&#xff1a; /** 费用管理 - IT费用管理 - 费用数据上传 */ import { useState } from "react"; import {…

NTLM Relay攻击原理 + 工具使用

前言 仅仅是记录自己看《域内攻防指南》的体会&&理解&#xff0c;具体的知识学习建议看windows protocol &#xff08;✨&#xff09; ✅&#xff1a;NTLM是不依赖于上层协议的&#xff01;&#xff01;&#xff01;NTLM起到的就是认证&#xff0c;只认证Client的身份…

并查集 (Union-Find) :从基础到优化

并查集 (Union-Find) 并查集是一种树形数据结构&#xff0c;主要用于处理不相交集合&#xff08;Disjoint Set&#xff09;的合并和查询问题。它特别适用于解决有关连通性的问题&#xff0c;比如在图论中判断两点是否在同一个连通分量中。并查集可以高效地支持以下两种操作&am…

个人博客系统测试(selenium)

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;Yan. yan.                        …

OceanBase 3.X 高可用 (一)

OceanBase 3.X 高可用&#xff08;一&#xff09; 一、分布式核心 OceanBase 3.x 采用的是paxos 协议&#xff0c;与raft协议相比。其复杂程度高&#xff0c;实现技术难度大。 Paxos 协议允许事务日志乱序发送&#xff0c;顺序提交。raft允许事务顺序发送&#xff0c;顺序提…

深度学习:常见损失函数简介--名称、作用和用法

目录 1. L1 Loss 2. NLL Loss (Negative Log Likelihood Loss) 3. NLLLoss2d 4. Gaussian NLL Loss 5. MSE Loss (Mean Squared Error Loss) 6. BCE Loss (Binary Cross-Entropy Loss) 7. Smooth L1 Loss 8. Cross Entropy Loss 1. L1 Loss 作用&#xff1a;计算预测值…

了解通用 SQL 语法

上世纪 90 年代中期&#xff0c;Sun Microsystems 公司推出了一种“一次编写&#xff0c;[随处]运行”的编程语言。这种语言就是 Java。尽管时至今日它仍然是最受欢迎的编程语言之一&#xff0c;但其口号却显得有些过于乐观。Java 语言的发展历程与 SQL 有着诸多相似之处。Java…

C语言常见字符串函数模拟实现一:(strlen,strcpy,strcat,strcmp,strstr )

strlen模拟实现 重点&#xff1a;1.字符串已经\0作为结束标志&#xff0c;strlen返回的是字符串\0前面出现的字符个数&#xff08;不包含\0&#xff09; 2.参数指向的字符串必须要以\0结束。 3.注意函数的返回值是size_t&#xff0c;是无符号的&#xff0c;加减是无法对比的。…

实用的云手机软件有哪些?高性价比云手机推荐

云手机不仅能模拟传统手机的功能&#xff0c;还能实现跨设备操作、数据同步等&#xff0c;极大地提升了用户的便利性。在众多云手机软件中&#xff0c;哪些软件表现出色呢&#xff1f;下面整理了一些功能强大、操作便捷且性能稳定的云手机APP&#xff0c;供大家参考选择。 1. O…

编程练习2 数据单元的变量替换

示例1: 1,2<A>00 示例2: 1,2<A>00,3<A>00 示例3: <B>12,1,2<B>1 示例4: <B<12,1 输出依次如下&#xff1a; #include<iostream> #include<vector> #include<string>using namespace std;/* 字符分割函数 将传入…

IIS中配置HTTPS证书的详细步骤

在IIS&#xff08;Internet Information Services&#xff09;中导入HTTPS证书的步骤主要包括下载证书、导入证书和为网站绑定证书几个环节。以下是详细的步骤说明&#xff1a; 一、下载SSL证书 首先&#xff0c;确保你已经从证书颁发机构&#xff08;CA&#xff09;下载了适…

三.python入门语法2

目录​​​​​​​ 1.控制结构 1.1.顺序结构 1.2.选择结构 习题 1.3.循环结构 1.3.1. while语句 1.3.2.for语句 1.3.3.循环嵌套 1.4.break语句 1.5.continue语句 1.6.pass语句 习题 1.控制结构 在学习控制结构之前我们通过一个故事来简单的描述一下控制结构&…

DAMODEL丹摩智算:LLama3.1部署与使用

文章目录 前言 一、LLaMA 3.1 的特点 二、LLaMA3.1的优势 三、LLaMA3.1部署流程 &#xff08;一&#xff09;创建实例 &#xff08;二&#xff09;通过JupyterLab登录实例 &#xff08;3&#xff09;部署LLaMA3.1 &#xff08;4&#xff09;使用教程 总结 前言 LLama3…