数据结构 树2

news2025/3/9 3:54:41

文章目录

前言

一,二叉搜索树的高度

 二,广度优先VS深度优先

三,广度优先的代码实现

四,深度优先代码实现

五,判断是否为二叉搜索树

六,删除一个节点

七,二叉收索树的中序后续节点

总结


前言

我们上一篇学习了树的基本知识和树的分类,二叉搜索树是作为我们学习的重点内容,所以我将继续深入学习二叉搜索树


一,二叉搜索树的高度

我们来复习一下树的高度和深度

树的高度节点到子叶节点的距离
树的深度节点到根节点的距离

接下来我们就要用代码来实现树的高度的计算

#include<iostream>

using namespace std;

struct BstNode {
	BstNode* left;
	int data;
	BstNode* right;
};

BstNode* Insert(BstNode* root, int x);
BstNode* GetNode(int x);
int Hight(BstNode* rtoot);

int main() {
	BstNode* root = NULL;
	root = Insert(root, 6);
	root = Insert(root, 9);
	root = Insert(root, 10);
	root = Insert(root, 8);

	root = Insert(root, 4);
	root = Insert(root, 5);
	root = Insert(root, 3);

	int hight = Hight(root);
	cout << hight << endl;
}

/*
               6
		    /     \
	       4       9
		 /   \   /   \
		3     5 8    10
*/

BstNode* Insert(BstNode* root, int x) {
	if (root == NULL) {
		root = GetNode(x);
		return root;
	}
	else if (root->data >= x) {
		root->left = Insert(root->left, x);
	}
	else if (root->data < x) {
		root->right = Insert(root->right, x);
	}
	return root;
}

BstNode* GetNode(int x) {
	BstNode* newNode = new BstNode();
	newNode->data = x;
	newNode->left = NULL;
	newNode->right = NULL;
	return newNode;
}

int Hight(BstNode* root) {
	if (root == NULL) {
		return -1;
	}

	int lefthight = Hight(root->left);
	int righthight = Hight(root->right);

	return max(lefthight, righthight) + 1;
}

代码所表示的树的结构图 

       6
    /     \
   4       9
 /   \   /   \
3     5 8    10

代码里的计算高度

int Hight(BstNode* root) {
	if (root == NULL) {
		return -1;
	}

	int lefthight = Hight(root->left);
	int righthight = Hight(root->right);

	return max(lefthight, righthight) + 1;
}

分析

我们这里如果根节点是空,高度就是-1,这个是规定的
max函数

如果没有接触过C++的,这个max是系统自己给的函数,你也可以自己写,就是我们以往写两个数字比较出最大的数字是一样的
递归

我们这里用的是递归的方法进行了计算,这里就是不断地寻找NULL的节点,我们拿4的计算为例子

左子树:

  • 左子树的根是 3
  • 3 是叶子节点,它没有左子树和右子树,所以它的左右子树的高度都为 0
  • 那么,3 的高度是 max(0, 0) + 1 = 1

右子树:

  • 右子树的根是 5
  • 5 是叶子节点,它同样没有左右子树
  • 所以,5 的高度是 max(0, 0) + 1 = 1

我们不难想出,再寻找NULL的时候,这个是每一个子树的左右子树都是要进行比较的,这个就是递归nb的地方,然后计算完了4,又会跑到右子树进行计算,然后最后把左右两个子树进行比较,这个后面的+1是给子树赋值的

 二,广度优先VS深度优先

树不再像我们之前所学的链表,栈,队列这种顺序结构了,它是一个非线性结构,所以树是有属于自己的遍历方法的

在访问树的节点过程中,这个是有先后顺序的,按照某个顺序进行访问,且每个节点只可以访问一次,对于这个遍历,我们有两种方法:1,深度优先    2,广度优先    3,图搜索技术
这个图搜索技术,我们还没有学完图,所以我们值考虑前两个方法

1,广度优先思想

我们会逐层访问

1,我们访问第一层,记录9
2,我们访问第二层,记录7,15
3,我们访问第三层,记录4,6,12,16
4,我们访问第四层,记录3,5,14
5,我们访问第五层,记录10
总的次序:9,7,15,4,6,12,16,3,5,14,10

先访问同一层次的节点,当访问完这个层次的节点,再到下一深度的层次继续访问

2,深度优先思想

对于这个我们有三种情况

前序<root><left><right>
中序<left><root><right>
后序<left><right><root>

第一个为先访问根,再访问左子树,再访问右子树 
顺序:9,7,4,3,5,6,15,12,14,10,16
第二个为先访问左子树,再访问根,再访问右子树 
顺序:7,4,3,5,6,9,15,12,14,10,16
第三个为先访问右子树,再访问左子树,再访问根 
顺序:7,4,3,5,6,15,12,14,10,16,9

就是把子树访问顺序

三,广度优先的代码实现

我们这种逐层的访问不就是很像队列么,先把根放入队列,然后放入孩子,然后把根弹出,再放入孩子的孩子,再把孩子弹出……

接下来我们来实现这个代码 

实际我们运行代码,代码会帮我们优化为这样

         9
       /   \
      7     15
     / \    /  \
    4   8  12   16
   / \     / \
  3   5   10  14

代码实现

#include<iostream>
#include<queue>

using namespace std;

struct BstNode {
	BstNode* left;
	int data;
	BstNode* right;
};

BstNode* Insert(BstNode* root, int x);
BstNode* GetNode(int x);
void leveread(BstNode* root);


int main() {
	BstNode* root = NULL;

	root = Insert(root, 9);  // 根节点
	root = Insert(root, 7);  // 左子树
	root = Insert(root, 15); // 右子树
	root = Insert(root, 4);  // 7 的左子树
	root = Insert(root, 8);  // 7 的右子树
	root = Insert(root, 12); // 15 的左子树
	root = Insert(root, 16); // 15 的右子树
	root = Insert(root, 3);  // 4 的左子树
	root = Insert(root, 5);  // 4 的右子树
	root = Insert(root, 10); // 12 的左子树
	root = Insert(root, 14); // 12 的右子树

	leveread(root);
}

BstNode* Insert(BstNode* root, int x) {
	if (root == NULL) {
		root = GetNode(x);
		return root;
	}
	else if (root->data > x) {
		root->left = Insert(root->left, x);
	}
	else if (root->data < x) {
		root->right = Insert(root->right, x);
	}
	return root;
}

BstNode* GetNode(int x) {
	BstNode* newNode = new BstNode();
	newNode->data = x;
	newNode->left = NULL;
	newNode->right = NULL;
	return newNode;
}

void leveread(BstNode* root) {
	if (root == NULL)return;
	queue<BstNode*>Q;
	Q.push(root);
	while (!Q.empty()){
		BstNode* current = Q.front();
		cout << current->data << " ";
		if (current->left!=NULL) {
			Q.push(current->left);
		}
		if (current -> right != NULL) {
			Q.push(current->right);
		}
		Q.pop();
	}
}

我们来看这个广度搜索的代码,最后面的函数就是把所有的元素放入到队列里面,然后再进行读取弹出,我们利用队列的顺序,一一的把这个孩子的孩子都放入到队列里面

时间为O(n)

四,深度优先代码实现

前序

         9
       /   \
      7     15
     / \    /  \
    4   8  12   16
   / \     / \
  3   5   10  14

代码实现

#include<iostream>
#include<queue>

using namespace std;

struct BstNode {
	BstNode* left;
	int data;
	BstNode* right;
};

BstNode* Insert(BstNode* root, int x);
BstNode* GetNode(int x);
void preread(BstNode* root);

int main() {
	BstNode* root = NULL;

	root = Insert(root, 9);  // 根节点
	root = Insert(root, 7);  // 左子树
	root = Insert(root, 15); // 右子树
	root = Insert(root, 4);  // 7 的左子树
	root = Insert(root, 8);  // 7 的右子树
	root = Insert(root, 12); // 15 的左子树
	root = Insert(root, 16); // 15 的右子树
	root = Insert(root, 3);  // 4 的左子树
	root = Insert(root, 5);  // 4 的右子树
	root = Insert(root, 10); // 12 的左子树
	root = Insert(root, 14); // 12 的右子树

	preread(root);
}

BstNode* Insert(BstNode* root, int x) {
	if (root == NULL) {
		root = GetNode(x);
		return root;
	}
	else if (root->data > x) {
		root->left = Insert(root->left, x);
	}
	else if (root->data < x) {
		root->right = Insert(root->right, x);
	}
	return root;
}

BstNode* GetNode(int x) {
	BstNode* newNode = new BstNode();
	newNode->data = x;
	newNode->left = NULL;
	newNode->right = NULL;
	return newNode;
}

void preread(BstNode* root) {
	if (root == NULL) {
		return;
	}
	cout << root->data << " ";
	preread(root->left);
	preread(root->right);
}

分析

这个就是把这个cout放到递归上面,为什么呢?如果不知道证明你基础太差了,回炉重造吧,然后进入递归,我们会把左边的全部都找完,因为只要有left为NULL则会返回去找左子树的右子树的值,然后进入的根的左子树的时候,就会跳到根的右子树进行查找,先进去右子树的左子树,然后再进入到右子树的右子树

中序跟后序跟这个前序差不多,这里放了代码,读者可以自行思考

中序

void Inread(BstNode* root) {
	if (root == NULL) {
		return;
	}
	Inread(root->left);
	cout << root->data << " ";
	Inread(root->right);
}

后序

void proread(BstNode* root) {
	if (root == NULL) {
		return;
	}
	proread(root->left);
	proread(root->right);
	cout << root->data << " ";
}

时间为O(n) 

五,判断是否为二叉搜索树

 下面这个代码的时间复杂度为O(n)

​
bool Isreallytree(BstNode* root,int minvalue,int maxvalue) {
	if (root == NULL)return true;
	if (
		root->data > minvalue
		&& root->data < maxvalue
		&& Isreallytree(root->left, minvalue, root->data)
		&& Isreallytree(root->right, root->data, maxvalue)
		)return true;
	else
		return false;
}

​

 分析
我们再if里面进行不断地判断

逻辑梳理

每次我进行递归地时候,先进行左子树的判断,然后对比这个左子树的最大值和最小值,这个最大值每次是需要不断地改进地,就是这个节点,然后当我们地左子树地左边全部判断完了之后,就会执行right语句,也就是去判断这个树地右边进行判断,然后再到根的右子树进行判断,最后决定是返回true还是false

六,删除一个节点

这个东西就比较复杂,因为我们删除之后还要恢复树的平衡

我们把删除的情况分为:1,删除子叶节点    2,删除有两个孩子的节点   3,删除有一个孩子的节点

1,删除子叶节点

我们可以看到,我们可以直接删除即可

直接删除

2,删除含有一个孩子的节点

 我们不难看出,当我们删除一个节点的时候,我们直接把这个节点删除,然后把这个节点跟上就好了,十分的简单,如果你怕有风险,可以试试3或者13,我们试一下,你会发现这都是没有问题的

先连接当前节点的下一个节点,然后再把这个节点删除

3,删除有两个孩子的节点

我们可以看到这个有两种方法

方法一:
就是把15这个节点删除,然后在左子树寻找最大值放到删除的节点
方法二:
就是把15这个节点删除,然后再右子树寻找最小值放到删除的节点

在左子树寻找最大值,在右子树寻找最大值

接下来我们就用代码来实现

BstNode* remove(BstNode* root, int data) {
	if (root == NULL) {
		return root;
	}
	else if (data < root->data) {
		root->left = remove(root->left, data);
	}
	else if (data < root->data) {
		root->right = remove(root->right, data);
	}
	else {
		//NO children
		if (root->left == NULL && root == NULL) {
			delete root;
			root = NULL;
			return root;
		}
		//One children
		else if (root->left == NULL) {
			BstNode* temp = root;
			root = root->right;
			delete temp;
			return root;
		}
		else if (root->right == NULL) {
			BstNode* temp = root;
			root = root->left;
			delete temp;
			return root;
		}
		//two children
		else {
			BstNode* temp = MAX(root->left);
			root->data = temp->data;
			root->left = remove(root->left, temp->data);
		}
	}
}

BstNode* MAX(BstNode* root) {
	if (root == NULL) {
		cout << "未找到" << endl;
		return NULL;
	}
	BstNode* current = root;
	while (current -> right != NULL) {
		current = current->right;
	}
	return current;
}

 这个MAX就不用多说了,这个就是不断地到树的最右边进行查找,我们来看看这个删除的代码

第一部分

if (root == NULL) {
	return root;
}
else if (data < root->data) {
	root->left = remove(root->left, data);
}
else if (data < root->data) {
	root->right = remove(root->right, data);
}

这个是我们进行查找到这个值,直到我们找到那个值跳到else语句里面,这个为什么前面要有一个root->left/right进行接收呢?这个就是我们上一篇文章讨论的问题,我们需要不断地接受新的子树,不可以还是为原来地子树

第二部分

//NO children
if (root->left == NULL && root == NULL) {
	delete root;
	root = NULL;
	return root;
}

这个是我直接删除叶子节点

第三部分

//One children
else if (root->left == NULL) {
	BstNode* temp = root;
	root = root->right;
	delete temp;
	return root;
}
else if (root->right == NULL) {
	BstNode* temp = root;
	root = root->left;
	delete temp;
	return root;
}

 这个是我们需要进行左右子树的删除我们root节点,这个节点我们用一个指针指着,然后把这个root节点改掉,改成指向root->right,然后返回这个root删除temp,左右子树都是一样的

第四部分

//two children
else {
	BstNode* temp = MAX(root->left);
	root->data = temp->data;
	root->left = remove(root->left, temp->data);
}

这个就是我们把这个最大值找打,然后赋予给这个删除的地方,接下来我们就要找到这个节点并且删除,当我们找到之后,我们就又可以判断这个节点是哪一种,如果为两个孩子还要进行递归,如果为其他两种情况的话就是结束了

这个就是把两个孩子变成一个孩子或者没有孩子的情况 

七,二叉收索树的中序后续节点

这个就是让你去判断你要使用中序的方法读取的那个节点的后面那个节点是多少,这个读者可以自行思考,后续也会更新这个代码的实现方法,这个就是让你用中序方法,假设为7,让你判断7后面那个几点为多少


总结

1,二叉搜索树的高度的计算
就是利用递归找到树的最低端头,先进行左子树的进行判断,然后在进行右子树的判断

2,广度优先VS深度优先
这两个就是,广度优先就是利用队列的方法把对应的推入到队列里面,然后再进行cout,然后利用pop弹出

3,判断是否为二叉搜索树
这个就是利用二叉树的性质,找到最大值和最小值,然后进行判断,运用递归,注意判断每个子树的时候要修改最值

4,删除节点
这个就是有三个方法,无孩子,1个孩子,2个孩子,2个孩子就是寻找最值然后再降为1个孩子或者没有孩子的情况

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

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

相关文章

NeetCode刷题第19天(2025.1.31)

文章目录 099 Maximum Product Subarray 最大乘积子数组100 Word Break 断字101 Longest Increasing Subsequence 最长递增的子序列102 Maximum Product Subarray 最大乘积子数组103 Partition Equal Subset Sum 分区等于子集和104 Unique Paths 唯一路径105 Longest Common Su…

Google Chrome-便携增强版[解压即用]

Google Chrome-便携增强版 链接&#xff1a;https://pan.xunlei.com/s/VOI0OyrhUx3biEbFgJyLl-Z8A1?pwdf5qa# a 特点描述 √ 无升级、便携式、绿色免安装&#xff0c;即可以覆盖更新又能解压使用&#xff01; √ 此增强版&#xff0c;支持右键解压使用 √ 加入Chrome增强…

[EAI-027] RDT-1B,目前最大的用于机器人双臂操作的机器人基础模型

Paper Card 论文标题&#xff1a;RDT-1B: a Diffusion Foundation Model for Bimanual Manipulation 论文作者&#xff1a;Songming Liu, Lingxuan Wu, Bangguo Li, Hengkai Tan, Huayu Chen, Zhengyi Wang, Ke Xu, Hang Su, Jun Zhu 论文链接&#xff1a;https://arxiv.org/ab…

[EAI-028] Diffusion-VLA,能够进行多模态推理和机器人动作预测的VLA模型

Paper Card 论文标题&#xff1a;Diffusion-VLA: Scaling Robot Foundation Models via Unified Diffusion and Autoregression 论文作者&#xff1a;Junjie Wen, Minjie Zhu, Yichen Zhu, Zhibin Tang, Jinming Li, Zhongyi Zhou, Chengmeng Li, Xiaoyu Liu, Yaxin Peng, Chao…

DIFY源码解析

偶然发现Github上某位大佬开源的DIFY源码注释和解析&#xff0c;目前还处于陆续不断更新地更新过程中&#xff0c;为大佬的专业和开源贡献精神点赞。先收藏链接&#xff0c;后续慢慢学习。 相关链接如下&#xff1a; DIFY源码解析

hexo部署到github page时,hexo d后page里面绑定的个人域名消失的问题

Hexo 部署博客到 GitHub page 后&#xff0c;可以在 setting 中的 page 中绑定自己的域名&#xff0c;但是我发现更新博客后绑定的域名消失&#xff0c;恢复原始的 githubio 的域名。 后面搜索发现需要在 repo 里面添加 CNAME 文件&#xff0c;内容为 page 里面绑定的域名&…

【Block总结】MAB,多尺度注意力块|即插即用

文章目录 一、论文信息二、创新点三、方法MAB模块解读1、MAB模块概述2、MAB模块组成3、MAB模块的优势 四、效果五、实验结果六、总结代码 一、论文信息 标题: Multi-scale Attention Network for Single Image Super-Resolution作者: Yan Wang, Yusen Li, Gang Wang, Xiaoguan…

移动互联网用户行为习惯哪些变化,对小程序的发展有哪些积极影响

一、碎片化时间利用增加 随着生活节奏的加快&#xff0c;移动互联网用户的碎片化时间越来越多。在等公交、排队、乘坐地铁等间隙&#xff0c;用户更倾向于使用便捷、快速启动的应用来满足即时需求。小程序正好满足了这一需求&#xff0c;无需下载安装&#xff0c;随时可用&…

使用 Tauri 2 + Next.js 开发跨平台桌面应用实践:Singbox GUI 实践

Singbox GUI 实践 最近用 Tauri Next.js 做了个项目 - Singbox GUI&#xff0c;是个给 sing-box 用的图形界面工具。支持 Windows、Linux 和 macOS。作为第一次接触这两个框架的新手&#xff0c;感觉收获还蛮多的&#xff0c;今天来分享下开发过程中的一些经验~ 为啥要做这个…

攻防世界_simple_php

同类型题&#xff08;更难版->&#xff09;攻防世界_Web(easyphp)&#xff08;php代码审计/json格式/php弱类型匹配&#xff09; php代码审计 show_source(__FILE__)&#xff1a;show_source() 函数用于显示指定文件的源代码&#xff0c;并进行语法高亮显示。__FILE__ 是魔…

C++哈希(链地址法)(二)详解

文章目录 1.开放地址法1.1key不能取模的问题1.1.1将字符串转为整型1.1.2将日期类转为整型 2.哈希函数2.1乘法散列法&#xff08;了解&#xff09;2.2全域散列法&#xff08;了解&#xff09; 3.处理哈希冲突3.1线性探测&#xff08;挨着找&#xff09;3.2二次探测&#xff08;跳…

Solon Cloud Gateway 开发:导引

Solon Cloud Gateway 是 Solon Cloud 体系提供的分布式网关实现&#xff08;轻量级实现&#xff09;。 分布式网关的特点&#xff08;相对于本地网关&#xff09;&#xff1a; 提供服务路由能力提供各种拦截支持 1、分布式网关推荐 建议使用专业的分布式网关产品&#xff0…

dmfldr实战

dmfldr实战 本文使用达梦的快速装载工具&#xff0c;对测试表进行数据导入导出。 新建测试表 create table “BENCHMARK”.“TEST_FLDR” ( “uid” INTEGER identity(1, 1) not null , “name” VARCHAR(24), “begin_date” TIMESTAMP(0), “amount” DECIMAL(6, 2), prim…

Spring AOP 入门教程:基础概念与实现

目录 第一章&#xff1a;AOP概念的引入 第二章&#xff1a;AOP相关的概念 1. AOP概述 2. AOP的优势 3. AOP的底层原理 第三章&#xff1a;Spring的AOP技术 - 配置文件方式 1. AOP相关的术语 2. AOP配置文件方式入门 3. 切入点的表达式 4. AOP的通知类型 第四章&#x…

Upscayl-官方开源免费图像AI增强软件

upscayl 链接&#xff1a;https://pan.xunlei.com/s/VOI0Szqe0fCwSSUSS8zRqKf7A1?pwdhefi#

SpringBoot Web开发(SpringMVC)

SpringBoot Web开发&#xff08;SpringMVC) MVC 核心组件和调用流程 Spring MVC与许多其他Web框架一样&#xff0c;是围绕前端控制器模式设计的&#xff0c;其中中央 Servlet DispatcherServlet 做整体请求处理调度&#xff01; . 除了DispatcherServletSpringMVC还会提供其他…

苍穹外卖第一天

角色分工 技术选型 pojo子模块 nginx反向代理 MD5密码加密

C# Winform enter键怎么去关联button

1.关联按钮上的Key事件按钮上的keypress&#xff0c;keydown&#xff0c;keyup事件随便一个即可private void textBox1_KeyDown(object sender, KeyEventArgs e){if (e.KeyCode Keys.Enter){this.textBox2.Focus();}}2.窗体上的事件private void textBox2_KeyPress(object sen…

LeGO LOAM坐标系问题的自我思考

LeGO LOAM坐标系问题的自我思考 IMU坐标系LeGO LOAM代码分析代码 对于IMU输出测量值的integration积分过程欧拉角的旋转矩阵VeloToStartIMU()函数TransformToStartIMU(PointType *p) IMU坐标系 在LeGO LOAM中IMU坐标系的形式采用前(x)-左(y)-上(z)的形式&#xff0c;IMU坐标系…

vim交换文件的作用

1.数据恢复&#xff1a;因为vim异常的退出&#xff0c;使用交换文件可以恢复之前的修改内容。 2.防止多人同时编辑&#xff1a;vim检测到交换文件的存在,会给出提示&#xff0c;以避免一个文件同时被多人编辑。 &#xff08;vim交换文件的工作原理&#xff1a;vim交换文件的工作…