数据结构 树1
目录
前言
一,树的引论
二,二叉树
三,二叉树的详细理解
四,二叉搜索树
五,二分法与二叉搜索树的效率
六,二叉搜索树的实现
七,查找最大值和最小值
指针传递 vs 传引用
为什么指针按值传递不会修改 root->left?
查到最值的功能
总结
前言
当我们运用链表,栈,队列等线性数据结构来进行搜索数据的时候,他们的时间复杂度都是O(n),我们不妨做一个小小的测试
假设 我们的一个机器可以做一百万次比较每秒
则我们的机器可以执行10^7比较
也就是每次的比较时间为10^-7秒
以上是我们假设一个机器可以做出查找操作的效率,接下来我们用这个机器放入到日常工作中,我们来看看情况,如今我们的数据量是十分惊人的,有着1亿或者10亿的数据量,我们计算一下这个机器处理10亿个数据的效率,运用链表,栈,队列这种数据结构来进行,时间复杂度为O(n)
计算:10亿个数据,我们最差的为10^10*10^-7=10^3s=16.7min,当然我们如果出现了16.7min,肯定是不行的,因为我们都想快点找到自己的数据,所以我们接下来就讲讲树这个结构
一,树的引论
目前我们所学习了链表,栈,队列这些都是线性结构或者说顺序结构
数据结构的选取
有了这么多的数据结构,我们该怎么选择呢?我们应当考虑以下几个方面
1,我们要选择什么样的数据类型
2,操作的成本
3,内存的消耗
4,数据结构实现的难度
树用于表示一种层次的数据结构
(tree)
树的基本结构介绍
树也可以被称为一个递归的数据结构
(树的基本实现方法就是递归)
这里的子树可以看成递归的深度,这个root为入口
树边的计算
一棵树有N个节点,那么这个树有N-1个节点(图中的每一个线可以表示一个链接或者一个边),除了根节点外,图中的每一个节点都有一个边进行对应
深度与高度
深度
节点到根节点的路径长度
高度 节点到子叶节点的路径长度
7 高度为0,深度为2 5 高度为1,深度为2 13 高度为0,深度为3 总的来说,深度为节点往根节点数,高度为节点往子叶节点数
二,二叉树
二叉树的概念
树上的节点最多只可以有两个子叶节点
(不会超过两个小孩,为左孩和右孩)
我们把我们的树换一个角度思考,换成我们所熟悉的样子来看待问题,这样我们就可以构想出一棵树是如何用我们所学过的知识来转化的,我们树中的孩子节点可以这样设置
struct BstNode {int data;BstNode* left;BstNode* right; };
基于树的应用
1,存储天然具备的层级的数据结构----电脑上面的磁盘驱动器上的文件系统
2,组织数据----在一个结构里面进行快速的查找与删除,二叉搜索树
3,Trie树----用于字典,用于一个动态的拼写检查
三,二叉树的详细理解
树的类型与属性
二叉树的通用属性:二叉搜索树的每一个节点的子节点不可以超过两个节点
严格二叉树: 每一个二叉树的子节点只可以为0个或者2个
完全二叉树:除了最后一层,其他层填满,并且最后一层的节点全部都是向左靠
这个就是一个完全二叉树的例子
完美二叉树:所有层都填满
二叉树里的计算
1,节点的最大数量:2^0+2^1+2^2+……+2^n=2^(n+1)-1
2,n个节点的完美二叉树的高度:n=2^(h+1)+1 h=
-1
3,n个节点的完全二叉树的高度:h=
也就是说时间复杂度为O(
)
4,n个节点的最大高度为(n-1)
所以我们一般把树的高度控制到最小,然后如果只有根节点高度为0,如果没有节点的话就是-1
5,diff=|lhight - rhight| lhight为左子树,rhight为右子树
实现可以用数组也可以用链表
四,二叉搜索树
二叉搜索树是一种可以高效进行搜索和更新的数据结构
之前数据结构的效率
抛问:你该使用什么数据结构进行存储一个可以修改的集合
搜索 插入 删除 类型 O(1) O(1)数组末尾 O(n) 数组 O(n)
O(1)链表头部
O(n) 链表 根据我们的前言可以知道,我们利用这两个类型不用什么好的方法的话,那么时间会花费的非常长,这个时候我们可以考虑用二分法,在两边分别放一个指针,这样我们的时间复杂度就是O(
)了,我们利用这个来看看我们在实际的时候,这个时间复杂度可以减多少呢?
例子
假设我们有2^31的数据量,这个数据量以及超过了20亿的数据量
n=2^31 则时间为31*10^-6秒,这个时间相比较上面那一个以及少量非常多的时间了
二叉树的查找成本为O(
)但是也有很差的情况O(n),但是这种情况是可以避免的,可以利用平衡二叉树
平衡二叉树的概念
平衡二叉树是一种自平衡的二叉搜索树,其中每个节点的左右子树的高度差(平衡因子)不超过1,这意味着树在任何时候都保持相对平衡的状态,避免了二叉树退化为链表的情况,从而确保了操作的时间复杂度
二叉搜索树
核心规则:
左子树的节点值小于当前节点的值
右子树的节点值大于当前节点的值
左右子树本身也必须是二叉搜索树
这个是一个二叉搜索树,我们来详细分析一下
5比7小 9比7大,符合
3比5小,比7小 6比5大,比7小 8比9小,比7大 10比9大,比7小符合
我们看这些是要进行比较的,而不是单单比较5和9,左右子树本身也必须是二叉搜索树
五,二分法与二叉搜索树的效率
我们不妨想想,二分法的时间复杂度是多少呢?如果忘记了二分法也没事,我写了一个很简单的二分法代码
二分法
#include<iostream>using namespace std;int search(int arr[], int sign, int n, int left, int right) {while (left <= right) {int mid = left + (right - left) / 2;if (arr[mid] == sign) {return 1;}else if (arr[mid] > sign) {right = mid - 1;}else {left = mid + 1;}}return -1; }int main() {int arr[9] = { 1,2,3,4,5,6,7,8,9 };int sign = 8;int n = sizeof(arr) / sizeof(arr[0]);int result = search(arr, sign, n, 0, n - 1);if (result == -1) {cout << "未找到" << endl;}else {cout << "找到了" << endl;}return 0; }
我们在使用二分法的时候,都是利用空间减半的方法,n->n/2->n/4->……,最后找到我们所想要的最终元素,如果没有找到则会left>right的情况,这个就可以判断是否要终止程序
n/2^k => 2^k = n => k =
二叉搜索树
其实你可以发现二叉搜索树也利用了这样的原理,我们可以来研究一下
我们要寻找3这个元素, 首先在根部进去进行寻找,然后进行分区,是比7大还是比7小,然后进行对空间的对半分,这不就是二分法么,基于这个思想,我们都很喜欢完美二叉树的出现,为什么呢?这不就是每一次分开的时候,都是对半分,这样效率特别高
我们这里思考了查找,但是我们还要有插入,删除的操作,那我们就需要思考怎么插入或者删除之后,把树平衡起来
六,二叉搜索树的实现
基本实现
#include<iostream>using namespace std;struct BstNode {BstNode* left;int data;BstNode* right; };BstNode* GetNode(int x) {BstNode* newNode = new BstNode();newNode->data = x;newNode->left = NULL;newNode->right = NULL;return newNode; }BstNode* Insert(BstNode* root, int x) {if (root == NULL) {root = GetNode(x);return root;}else if (x > root->data) {Insert(root->right, x);}else if (x < root->data) {Insert(root->left, x);}return root; }bool search(BstNode* root,int x) {if (root == NULL) {return false;}else if (root->data == x) {return true;}else if (root->data >= x) {return search(root->left, x);}else {return search(root->right, x);} }int main() {BstNode* root = NULL;root = Insert(root, 10);root = Insert(root, 6);root = Insert(root, 7);bool key = search(root, 10);if (key == true) {cout << "找到了" << endl;}else {cout << "未找到" << endl;} }
这里是用写了插入和搜索,这个main函数里面一定要用root来接收,因为root在其他地方为形参,改变不了形参,如果不想这么麻烦也可以设置一个全局变量,我们重点来理解一下这个递归,我们理解了插入的递归,这个搜索也是可以迎刃而解
细讲递归操作
BstNode* Insert(BstNode* root, int x) {if (root == NULL) {root = GetNode(x);return root;}else if (x > root->data) {Insert(root->right, x);}else if (x < root->data) {Insert(root->left, x);}return root; }
这里一开始root肯定是为空的,所以我们要给根节点赋予一个节点,然后下一次的时候就是判断这个节点是放到左子树还是右子树,我们这里用递归来进行树的深入
到了下一次,我们就会到左子树还是右子树,注意此时进入递归了,这个root不是你根节点了,是左子树或者右子树的根节点,然后当我找到left或者right为NULL的时候,我可以用第一个if语句给这个节点赋予一个节点了,重中之重此时的root不是根节点,然后赋予完返回根节点
然后进入到上一次的递归进行返回,最终递归逐个破解,返回的是这个树的根节点很好这里讲的是错误的,这是作者初代代码,后面有正确的改进,也可以给读者看看错误的递归思路,确实有点绕,此代码也有细小的问题,读者可以先自己思考哪里错误,后面再看正确的思路
放到堆和栈的策略
这个就涉及大到一个变量的生命周期的问题了,大致就是你想临时的就放到栈里,你想长期的就放到堆里
七,查找最大值和最小值
这个十分的简单哈,真的,你会发现这个最大值不就是最右边的那个么,这个最小值不就是最左边那个么,我们来实现一下
#include<iostream>using namespace std;struct BstNode {BstNode* left;int data;BstNode* right; };BstNode* GetNode(int x) {BstNode* newNode = new BstNode();newNode->data = x;newNode->left = NULL;newNode->right = NULL;return newNode; }BstNode* Insert(BstNode* root, int x) {if (root == NULL) {root = GetNode(x);return root;}else if (x > root->data) {root->right = Insert(root->right, x);}else if (x < root->data) {root->left = Insert(root->left, x);}return root; }bool search(BstNode* root,int x) {if (root == NULL) {return false;}else if (root->data == x) {return true;}else if (root->data >= x) {return search(root->left, x);}else {return search(root->right, x);} }BstNode* MAX(BstNode* root) {if (root == NULL) {cout << "未找到" << endl;return NULL;}BstNode* current = root;while (current -> right != NULL) {current = current->right;}return current; }int main() {BstNode* root = NULL;root = Insert(root, 10);root = Insert(root, 6);root = Insert(root, 7);root = Insert(root, 15);root = Insert(root, 14);root = Insert(root, 20);bool key = search(root, 7);if (key == true) {cout << "找到了" << endl;}else {cout << "未找到" << endl;}cout << MAX(root)->data << endl; }
这里是既有修正的代码和寻找最值的代码,我会一 一讲述
错误的点
BstNode* Insert(BstNode* root, int x) {if (root == NULL) {root = GetNode(x);return root;}else if (x > root->data) {root->right = Insert(root->right, x);}else if (x < root->data) {root->left = Insert(root->left, x);}return root; }
这里为什么不可以利用root = GetNode(x);来给左边和右边进行赋值呢?
假设你插入
6
到如下 BST:10/ \NULL NULL
执行:
root = Insert(root, 6);
Insert(root, 6);
root = 10
6 < 10
,调用Insert(root->left, 6);
Insert(root->left, 6);
root->left == NULL
,进入:if (root == NULL) {root = GetNode(x); // root 现在指向了新节点 6return root; }
- 但
root
是 局部变量,修改root
不会修改root->left,这里不可以进行对于左边的赋值操作
- 这一步返回了新节点
6
,但Insert(root->left, 6);
的返回值被丢弃了,没有赋值给root->left
递归结束后,原来的
root->left
仍然是NULL
,导致插入失败但是又想,奇怪,我传的是指针呀,为什么是值操作嘞?
指针传递 vs 传引用
在 C++ 里,指针本身是 按值传递 的
换句话说,Insert(root->left, x);
传递的root->left
是它的拷贝,而不是原始变量的引用Insert(root->left, x); // 你以为 root->left 会被修改
我觉得
root->left
传递给Insert
后,在Insert
内部的root = GetNode(x);
可以直接修改root->left
实际发生的情况:
void Insert(BstNode* root, int x);
root->left
的值(即NULL
)被传递给Insert
- 进入
Insert(root->left, x);
,这里的root
只是root->left
的拷贝root = GetNode(x);
只是修改了 拷贝的root
,不会修改root->left
- 递归结束后,
root->left
仍然是NULL
为什么指针按值传递不会修改
root->left
?来看一个简单的示例
void ChangePointer(int* ptr) {ptr = new int(10); // 只修改了 ptr 的拷贝 }int main() {int* p = NULL;ChangePointer(p);cout << p << endl; // 还是 NULL,没有变! }
这里的
ChangePointer(p);
不会修改p
,因为p
作为参数传进去时,只是它的拷贝,所以ptr = new int(10);
不会影响外部的p
这里也就是我们很常见的指针问题,我们这放了个形参,但不是全局变量,是局部的,这里确实再堆里面创建了一个节点,但是没有东西接收,所以会为NULL,上面这个简单的例子就阐明了
查到最值的功能
BstNode* MAX(BstNode* root) {if (root == NULL) {cout << "未找到" << endl;return NULL;}BstNode* current = root;while (current -> right != NULL) {current = current->right;}return current; }
我们这里只需要一直往右边就可以找到最大值了
总结
我们总结一下我们所学的东西
1,树的概念和树的基本知识
2,树的高度为节点到子叶的距离,深度为节点到根部的距离
3,二叉树的概念,需注意核心规则
4,二叉树的分类:严格二叉树,完全二叉树,完美二叉树,平衡二叉树
5,二叉树与二分法的关系和两个的具体实现
6,实现的时候遇到了一个bug为指针传递和传引用的区别,两个是不一样的传指针其实也就是一个形参罢了,引用不一样,这个是再原来的地方做法,也就是再常量区,这个需要学过C++才知道
7,查找最值的方法
相关文章:

数据结构 树1
目录 前言 一,树的引论 二,二叉树 三,二叉树的详细理解 四,二叉搜索树 五,二分法与二叉搜索树的效率 六,二叉搜索树的实现 七,查找最大值和最小值 指针传递 vs 传引用 为什么指针按值传递不会修…...

android主题设置为..DarkActionBar.Bridge时自定义DatePicker选中日期颜色
安卓自定义DatePicker选中日期颜色 背景:解决方案:方案一:方案二:实践效果: 背景: 最近在尝试用原生安卓实现仿element-ui表单校验功能,其中的的选择日期涉及到安卓DatePicker组件的使用&#…...

MySQL 如何深度分页问题
在实际的数据库应用场景中,我们常常会遇到需要进行分页查询的需求。对于少量数据的分页查询,MySQL 可以轻松应对。然而,当我们需要进行深度分页(即从大量数据的中间位置开始获取少量数据)时,就会面临性能严…...

1.攻防世界easyphp
进入题目页面如下 是一段PHP代码进行代码审计 <?php // 高亮显示PHP文件源代码 highlight_file(__FILE__);// 初始化变量$key1和$key2为0 $key1 0; $key2 0;// 从GET请求中获取参数a的值,并赋值给变量$a $a $_GET[a]; // 从GET请求中获取参数b的值ÿ…...

深度学习 Pytorch 神经网络的学习
本节将从梯度下降法向外拓展,介绍更常用的优化算法,实现神经网络的学习和迭代。在本节课结束将完整实现一个神经网络训练的全流程。 对于像神经网络这样的复杂模型,可能会有数百个 w w w的存在,同时如果我们使用的是像交叉熵这样…...

如何利用天赋实现最大化的价值输出-补
原文: https://blog.csdn.net/ZhangRelay/article/details/145408621 如何利用天赋实现最大化的价值输出-CSDN博客 如何利用天赋实现最大化的价值输出-CSDN博客 引用视频差异 第一段视频目标明确,建议也非常明确。 录制视频的人是主动性…...

Vue简介
目录 Vue是什么?为什么要使用Vue?Vue的三种加载方式拓展:什么是渐进式框架? Vue是什么? Vue是一套用于构建用户界面的渐进式 JavaScript (主张最少)框架 ,开发者只需关注视图层。另一方面,当与…...

three.js+WebGL踩坑经验合集(6.2):负缩放,负定矩阵和行列式的关系(3D版本)
本篇将紧接上篇的2D版本对3D版的负缩放矩阵进行解读。 (6.1):负缩放,负定矩阵和行列式的关系(2D版本) 既然three.js对3D版的负缩放也使用行列式进行判断,那么,2D版的结论用到3D上其实是没毛病的,THREE.Li…...

使用 OpenResty 构建高效的动态图片水印代理服务20250127
使用 OpenResty 构建高效的动态图片水印代理服务 在当今数字化的时代,图片在各种业务场景中广泛应用。为了保护版权、统一品牌形象,动态图片水印功能显得尤为重要。然而,直接在后端服务中集成水印功能,往往会带来代码复杂度增加、…...

Kafka下载
一、Kafka下载 下载地址:https://kafka.apache.org/downloads 二、Kafka安装 因为选择下载的是 .zip 文件,直接跳过安装,一步到位。 选择在任一磁盘创建空文件夹(不要使用中文路径),解压之后把文件夹内容…...

【C++语言】卡码网语言基础课系列----5. A+B问题VIII
文章目录 练习题目AB问题VIII具体代码实现 小白寄语诗词共勉 练习题目 AB问题VIII 题目描述: 你的任务是计算若干整数的和。 输入描述: 输入的第一行为一个整数N,接下来N行每行先输入一个整数M,然后在同一行内输入M个整数。 输出…...

IP服务模型
1. IP数据报 IP数据报中除了包含需要传输的数据外,还包括目标终端的IP地址和发送终端的IP地址。 数据报通过网络从一台路由器跳到另一台路由器,一路从IP源地址传递到IP目标地址。每个路由器都包含一个转发表,该表告诉它在匹配到特定目标地址…...

仿真设计|基于51单片机的温湿度、一氧化碳、甲醛检测报警系统
目录 具体实现功能 设计介绍 51单片机简介 资料内容 仿真实现(protues8.7) 程序(Keil5) 全部内容 资料获取 具体实现功能 (1)温湿度传感器、CO传感器、甲醛传感器实时检测温湿度值、CO值和甲醛值进…...

QModbusTCPClient 服务器断开引起的程序崩溃
最近使用QModbusTCPClient 与一套设备通信,有一个QTimer频繁的通过读取设备寄存器。程序运行良好,但是有个问题:正常进行中设备断电了,整个程序都会崩溃。解决过程如下: 1.失败方案一 在QModbusTCPClient的errorOccu…...

Vue 3 30天精进之旅:Day 11 - 状态管理
在开发复杂的前端应用时,状态管理是一个不可或缺的部分。Vuex是Vue.js官方提供的状态管理库,能够高效地管理应用中的共享状态。它使得各个组件之间的状态共享和维护变得更加简单和清晰。今天,我们将探讨以下几个方面: Vuex概述安…...

npm 和 pip 安装中常见问题总结
安装路径的疑惑:NPM 和 PIP 的安装机制 NPM 安装路径规则: 依赖安装在项目目录下: 当你运行 npm install --save-dev jest,它会在当前目录(例如 F:\)下创建一个 node_modules 文件夹,把 jest 安…...

Flutter开发环境配置
下载 Flutter SDK 下载地址:https://docs.flutter.cn/get-started/install M1/M2芯片选择带arm64字样的Flutter SDK。 解压 cd /Applications unzip ~/Downloads/flutter_macos_arm64_3.27.3-stable.zip执行 /Applications/flutter/bin/flutterManage your Flut…...

Two Divisors ( Educational Codeforces Round 89 (Rated for Div. 2) )
Two Divisors ( Educational Codeforces Round 89 (Rated for Div. 2) ) You are given n n n integers a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1,a2,…,an. For each a i a_i ai find its two divisors d 1 > 1 d_1 > 1 d1…...

亚博microros小车-原生ubuntu支持系列:17 gmapping
前置依赖 先看下亚博官网的介绍 Gmapping简介 gmapping只适用于单帧二维激光点数小于1440的点,如果单帧激光点数大于1440,那么就会出【[mapping-4] process has died】 这样的问题。 Gmapping是基于滤波SLAM框架的常用开源SLAM算法。 Gmapping基于RBp…...

Java面试题2025-并发编程进阶(线程池和并发容器类)
线程池 一、什么是线程池 为什么要使用线程池 在开发中,为了提升效率的操作,我们需要将一些业务采用多线程的方式去执行。 比如有一个比较大的任务,可以将任务分成几块,分别交给几个线程去执行,最终做一个汇总就可…...

Stable Diffusion 3.5 介绍
Stable Diffusion 3.5 是由 Stability AI 推出的最新一代图像生成模型,是 Stable Diffusion 系列的重要升级版本。以下是关于 Stable Diffusion 3.5 的详细信息: 版本概述 Stable Diffusion 3.5 包含三个主要版本: Stable Diffusion 3.5 L…...

云计算部署模式全面解析
目录 引言公有云私有云混合云三种部署模式的对比选择建议未来趋势结语 1. 引言 随着云计算技术的快速发展,企业在选择云部署模式时面临着多种选择。本文将深入探讨云计算的三种主要部署模式:公有云、私有云和混合云,帮助读者全面了解它们的特点、优势及适用场景。 © iv…...

Vue 与 Electron 结合开发桌面应用
1. 引言 1.1 什么是 Electron? Electron 是一个使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用程序的框架。它结合了 Chromium 渲染引擎和 Node.js 运行时,使得开发者可以使用 Web 技术创建原生桌面应用。1.2 为什么选择 Vue.js 和 Electron? Vue.js 是一个渐进式 JavaSc…...

数据库优化:提升性能的关键策略
1. 引言 在后端开发中,数据库的性能直接影响系统的稳定性和响应速度。随着业务增长,数据库查询变慢、负载过高等问题可能会影响用户体验。 本文将介绍数据库优化的关键策略,包括索引优化、查询优化、分库分表、缓存机制等,并结合…...

使用openAI与Deepseek的感受
今天简单介绍下使用OpenAI和DeepSeek的感觉,有些地方可能存在不准确的地方,望指正: 从2023年的秋冬到现在2025年的1月间,OpenAI和DeepSeek我都用它们来帮我,当然更多的是OpenAI,但整体感受如下:…...

pytorch实现长短期记忆网络 (LSTM)
人工智能例子汇总:AI常见的算法和例子-CSDN博客 LSTM 通过 记忆单元(cell) 和 三个门控机制(遗忘门、输入门、输出门)来控制信息流: 记忆单元(Cell State) 负责存储长期信息&…...

【ubuntu】双系统ubuntu下一键切换到Windows
ubuntu下一键切换到Windows 1.4.1 重启脚本1.4.2 快捷方式1.4.3 移动快捷方式到系统目录 按前文所述文档,开机默认启动ubuntu。Windows切换到Ubuntu直接重启就行了,而Ubuntu切换到Windows稍微有点麻烦。可编辑切换重启到Windows的快捷方式。 1.4.1 重启…...

【PyTorch】6.张量形状操作:在深度学习的 “魔方” 里,玩转张量形状
目录 1. reshape 函数的用法 2. transpose 和 permute 函数的使用 4. squeeze 和 unsqueeze 函数的用法 5. 小节 个人主页:Icomi 专栏地址:PyTorch入门 在深度学习蓬勃发展的当下,PyTorch 是不可或缺的工具。它作为强大的深度学习框架&am…...

大模型GUI系列论文阅读 DAY4续:《Large Language Model Agent for Fake News Detection》
摘要 在当前的数字时代,在线平台上虚假信息的迅速传播对社会福祉、公众信任和民主进程构成了重大挑战,并影响着关键决策和公众舆论。为应对这些挑战,自动化假新闻检测机制的需求日益增长。 预训练的大型语言模型(LLMs࿰…...

论文阅读(九):通过概率图模型建立连锁不平衡模型和进行关联研究:最新进展访问之旅
1.论文链接:Modeling Linkage Disequilibrium and Performing Association Studies through Probabilistic Graphical Models: a Visiting Tour of Recent Advances 摘要: 本章对概率图模型(PGMs)的最新进展进行了深入的回顾&…...