【数据结构】二叉树、堆

文章目录

  • 二叉树的概念及结构
    • 定义
    • 特殊的二叉树
    • 核心性质
    • 存储方式
  • 二叉树的链式存储
    • 前序遍历
    • 中序遍历
    • 后序遍历
    • 层序遍历
  • 二叉树的顺序存储
    • 父子关系的推导
    • 堆(heap)
      • 堆的概念
      • 向上调整算法和向下调整算法
        • 向上调整算法
        • 向下调整算法
      • 堆的创建
      • 堆的插入
      • 堆的删除
    • 堆的应用
      • 堆排序
      • TOP-K 问题

二叉树的概念及结构

定义

二叉树是每个节点最多有两个子节点的树结构,分别称为左子节点右子节点。子树区分左右顺序,即使仅有一个子节点也需明确方向。二叉树可以为空(无节点)。

特殊的二叉树

  • 满二叉树:所有非叶子节点均有左右子节点,且所有叶子在同一层。
  • 完全二叉树:除最后一层外,其他层节点全满,最后一层从左到右连续填充。
  • 平衡二叉树:任意节点左右子树高度差不超过1(如AVL树)。
  • 二叉搜索树(BST):左子树节点均小于根,右子树节点均大于根。

核心性质

  1. 节点关系:
    • 每个节点最多有两个子节点
    • 若树的高度为 h,则最大节点数为 2h - 1(即满二叉树的情况)
  2. 层次与深度:
    • 根节点位于第一层 (或第零层,依定义不同)
    • 根节点的深度为从根节点到该节点的路径长度

存储方式

  1. 链式存储:
    • 节点包含数据、左指针和右指针。
    • 灵活,适合动态增删。
  2. 顺序存储(数组):
    • 适用于完全二叉树,父节点下标 i 的左子节点为 2i,右子节点为 2i + 1
    • 非完全二叉树可能浪费存储空间。

二叉树的链式存储

根据定义,我们很容易就知道二叉树节点分为一个数据域和两个分别指向左右子树的指针域,于是就有:

typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}TreeNode;

对于链式结构的二叉树,我们主要得掌握它的三种深度优先遍历和一种层序遍历的方式
对于三种深度优先遍历,它这个前、中、后指的是根节点的前、中、后。

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根节点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根节点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根节点的操作发生在遍历其左右子树之后。

前序遍历

因为是递归调用,所以代码极其简单

void PrevOrder(TreeNode* root)
{if (root == NULL){printf("N\n");return;}printf("%d ", root->data); // 访问根节点PrevOrder(root->left);	   // 访问左子树PrevOrder(root->right);	   // 访问右子树
}

中序遍历

void InOrder(TreeNode* root)
{if (root == NULL){printf("N\n");return;}InOrder(root->left);		// 访问左子树printf("%d ", root->data);	// 访问根节点InOrder(root->right);		// 访问右子树
}

后序遍历

void PostOrder(TreeNode* root)
{if (root == NULL){printf("N\n");return;}PostOrder(root->left);		// 访问左子树PostOrder(root->right);		// 访问右子树printf("%d ", root->data);	// 访问根节点
}

从代码中,我们不难看出,这三种遍历方式仅是访问根节点的时机不同。

层序遍历

对于层序遍历,也就是字面意思,我们需要按层逐层访问节点。
那么如何做到呢?
这就得使用一个队列去保存树中的节点。
首先让根节点入队列,当队列不为空的时候,取出队列中队头节点,访问该节点的元素。然后判断该节点是否存在左右节点,存在则入队列。重复这个过程直到队列为空

vector<int> breadthFirstTraversal(TreeNode* root) {vector<int> result;		   // 用于存放层序遍历的结果if (!root) return result;  // 处理空树情况queue<TreeNode*> q;		   // 创建队列q.push(root);			   // 根节点入队列while (!q.empty()) {	   // 直到队列为空TreeNode* current = q.front();	// 取队头节点q.pop();						result.push_back(current->val);	// 将该节点的元素放入数组中if (current->left) q.push(current->left);	// 存在左节点就入队列if (current->right) q.push(current->right);	// 存在右节点就入队列}return result;
}

注释写的已经很清楚了,应该能看懂吧。
考虑到可能有些同学刚接触二叉树,可能看不懂这代码。没事,先有个印象就行了,等到后面对于数据结构的理解加深自然就理解了。

二叉树的顺序存储

普通的二叉树并不适合使用顺序存储,因为这可能会导致大量的空间浪费。在使用顺序存储时,一定是完全二叉树。
父子下标关系本质上是完全二叉树层序遍历在数组中的直接映射:

  • 左子下标 = 父下标 x 2(根从1开始) 或 父下标 x 2 + 1(根从0开始)
  • 右子下标 = 左子下标 + 1

父子关系的推导

因为数组是从 0 下标开始的,所以这里就以根节点从 0 开始推导

  1. 完全二叉树的层次遍历特性
    完全二叉树的节点按层次遍历顺序连续存储在数组中,且满足以下性质:
  • 第 k 层的节点数:2k
  • 第 k 层的起始下标:2k - 1
  • 第 k 层的第 m 个节点的下标:2k - 1 + m
  1. 父子节点位置关系
  • 父节点在第 k 层第 m 个位置:
    • 下标:i = 2k - 1 + m
  • 左子节点在第 k+1 层的第 2m 个位置:
    • 下标:left_child = 2k+1 - 1 + 2m
    • 代入 i = 2 k − 1 + m i=2^k-1+m i=2k1+m,化简得:
      left_child = 2i + 1
  • 右子节点为左子节点下标 + 1
    • right_child = 2i + 2

推导就到这里,数学好点的同学可以使用数学归纳法来证明一下。markdown的语法不太好写,这里就不再证明了

堆(heap)

说起二叉树的顺序存储就不得不提堆了。如果你存的数据都是些非常杂乱的,且你对它并没有做出些什么修正,那你用二叉树干嘛?搞得这么花里胡哨,不如直接使用数组。而堆就不一样了。

堆的概念

堆(Heap)是一种特殊的完全二叉树,具有以下核心性质,可分为 最大堆最小堆 两种类型:

性质:

  1. 结构性:完全二叉树
  • 堆在逻辑上是完全二叉树,所有层(除最后一层)节点全满,最后一层节点从左到右连续填充。
  • 顺序存储实现:通常用数组存储,利用下标关系快速定位父子节点:

  1. 堆序性:节点值的有序性
  • 最大堆(Max-Heap):
    • 每个节点的值 ≥ 其子节点的值。
    • 根节点是树中的最大值。
  • 最小堆(Min-Heap):
    • 每个节点的值 ≤ 其子节点的值。
    • 根节点是树中的最小值。

  1. 核心操作与性质维护
    堆通过以下操作维护其性质:
    1. 插入(Insert):
      • 新元素插入末尾,通过上浮(Percolate Up)调整位置。
      • 时间复杂度:O(logN)
    2. 删除堆顶(Extract-Max/Min):
      • 移除堆顶元素,将末尾元素移至堆顶,通过下沉(Percolate Down)调整位置。
      • 时间复杂度:O(logN)

向上调整算法和向下调整算法

在建堆之前,我们需要理解向上调整和向下调整算法。
以小堆为例

向上调整算法

向上调整算法,听名字就知道是向上比较,那么就是孩子节点(我们选择的节点)与父节点的比较。
使用场景:插入新元素后,将其从堆尾逐步向上调整到合适位置

void AdjustUp(HDataType* a, int child)
{int parent = (child - 1) / 2;	// 通过下标关系计算出父节点下标while (child > 0)				// 当子节点下标大于 0 时就继续调整{if (a[child] < a[parent])	// 这里以小堆为例,所以子小于父的时候交换两节点数据,将小的元素往上调{Swap(&a[parent], &a[child]);child = parent;parent = (child - 1) / 2;}else{break; // 到达合适位置的时候跳出循环}}
}
向下调整算法

向下调整需要一直调整到叶子节点

void AdjustDown(HDataType* a, int n, int parent)
{// 假设左孩子小int child = parent * 2 + 1;while (child < n) // child >= n说明孩子不存在,调整到叶子了{// 找出小的那个孩子,确保child指向的是小的孩子节点if (child + 1 < n && a[child + 1] < a[child]){++child;}if (a[child] < a[parent])	// 孩子节点比父节点小就进行交换{Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;	// 到合适的位置跳出循环}}
}

堆的创建

建堆有两种方法:

  • 一种是不断插入新节点(新节点肯定是叶子节点啦),插入一个节点就对其进行一次向上调整,直到建好堆。
  • 还有一种是将已知的数组视为一个堆,从最后一个非叶子节点开始进行向下调整,直到调整到根节点。

那么这两种建堆方式推荐使用哪种呢?
推荐使用向下调整的方式建堆!

为什么呢?因为更快。
这里很明确的告诉你向上调整建堆的时间复杂度是 O(Nlog N),而向下调整建堆的时间复杂度是 O(N)。
具体的证明这里就不写了,有兴趣的同学可以自己去推导一下,这里只说明个大概。

向上调整建堆,越深的节点,它要调整的次数越多(根节点到它的距离),并且越深的节点数量越多(在满二叉树的情况下)。
而向下调整建堆,越深的节点,它要调整的次数越少(它到叶子节点的距离),并且越深的节点数量越多。
那么在二者数量相同的情况下,向上调整:
(一层里)数量多的节点要调整的次数多,(一层里)数量少的节点调整的次数少。
向下调整:
(一层里)数量多的节点要调整的次数少,(一层里)数量少的节点调整的次数多。
很明显向下调整的次数更少,并且向下调整本身要调整的节点数量就小于向上调整要调整的节点数量,很明显向下调整建堆更快。

堆的插入

插入位置是在叶子节点,直接将其向上调整

void HeapPush(Heap* ph, HDataType x)
{assert(ph);if (ph->size == ph->capacity){int newcapacity = ph->capacity == 0 ? 4 : ph->capacity * 2;HDataType* tmp = (HDataType*)realloc(ph->a, newcapacity * sizeof(HDataType));if (tmp == NULL){perror("realloc fail!\n");return;}ph->a = tmp;ph->capacity = newcapacity;}ph->a[ph->size++] = x;AdjustUp(ph->a, ph->size - 1);
}

堆的删除

因为堆删除只能删除堆顶元素。
将堆顶元素和堆尾元素交换,删除掉原先的堆顶元素,再将新的堆顶元素向下调整即可。
解释:
以小堆为例,原先堆顶元素是整个堆中最小的,而堆尾元素肯定是大于原先的堆顶的,且会大于中间层的几个元素,将其放到堆顶,而向下调整又是将父节点的两个子节点中更小的那个调整上去,自然就能够保证堆顶为新的最小元素。

void HeapPop(Heap* ph)
{assert(ph);assert(ph->size > 0);Swap(&ph->a[0], &ph->a[ph->size - 1]);ph->size--;AdjustDown(ph->a, ph->size, 0);
}

堆的应用

堆排序

很明显,是将数组排序,那么只要对需要排序的数组进行向下调整建堆,分为两个步骤:

  1. 建堆
    • 升序:建大堆
    • 降序:建小堆
  2. 利用堆删除的思想来进行排序

这两个步骤都是使用了向下调整算法,所以其实理解了向下调整算法,那么堆排序也挺简单的。

TOP-K 问题

找出 n 个元素中,前 K 大的元素。
直接建堆再取 K 次堆顶即可。
当然,如果 n 的值非常非常大,也可以维护一个 K 大小的堆,对整个数据遍历一遍,最后剩下来的 K 个数据便是我们所需要的答案。

我记得前几年的蓝桥杯省赛中 c++ b、c组中就有几题需要用到这个,就自己实现一下 node 节点,再不断地对堆顶进行操作,出堆,入堆,有兴趣的话也可以自己去找来看看。

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

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

相关文章

Vue3响应式原理那些事

文章目录 1 响应式基础:Proxy 与 Reflect1.1 Proxy 代理拦截1.2 Reflect 确保 `this` 指向正确1.2.1 修正 `this` 指向问题1.2.2 统一的操作返回值1.3 与 Vue2 的对比2 依赖收集与触发机制2.1 全局依赖存储结构:WeakMap → Map → Set2.2 依赖收集触发时机2.3 依赖收集核心实…

精选10个好用的WordPress免费主题

10个好用的WordPress免费主题 1. Astra Astra 是全球最受欢迎的WordPress免费主题。它功能丰富&#xff0c;易于使用&#xff0c;SEO友好&#xff0c;是第一个安装量突破100万的非默认主题&#xff0c;并获得了5000多个五星好评。 它完美集成了Elementor、Beaver&#xff0c;…

【SaaS多租架构】数据隔离与性能平衡

SaaS多租户架构:数据隔离与性能平衡 一、技术背景及发展二、技术特点:数据隔离与性能优化的双核心三、技术细节:实现路径与关键技术四、实际案例分析五、未来发展趋势结语一、技术背景及发展 多租户架构是云计算与SaaS(软件即服务)模式的核心技术,其核心目标是通过共享基…

部署GM DC Monitor 一体化监控预警平台

1&#xff09;首先在官网下载镜像文件 广目&#xff08;北京&#xff09;软件有限公司广目&#xff08;北京&#xff09;软件有限公司https://www.gm-monitor.com/col.jsp?id1142&#xff09;其次进行部署安装&#xff0c;教程如下&#xff1a; 1. 基础环境要求 1) 系统&…

Webug4.0靶场通关笔记15- 第19关文件上传(畸形文件)

目录 第19关 文件上传(畸形文件) 1.打开靶场 2.源码分析 &#xff08;1&#xff09;客户端源码 &#xff08;2&#xff09;服务器源码 3.渗透实战 &#xff08;1&#xff09;构造脚本 &#xff08;2&#xff09;双写绕过 &#xff08;3&#xff09;访问脚本 本文通过《…

架构思维:构建高并发读服务_热点数据查询的架构设计与性能调优

文章目录 一、引言二、热点查询定义与场景三、主从复制——垂直扩容四、应用内前置缓存4.1 容量上限与淘汰策略4.2 延迟刷新&#xff1a;定期 vs. 实时4.3 逃逸流量控制4.4 热点发现&#xff1a;被动 vs. 主动 五、降级与限流兜底六、前端&#xff0f;接入层其他应对七、模拟压…

宝塔面板运行docker的jenkins

1.在宝塔面板装docker&#xff0c;以及jenkins 2.ip:端口访问jenkins 3.获取密钥&#xff08;点击日志&#xff09; 4.配置容器内的jdk和maven环境&#xff08;直接把jdk和maven文件夹放到jenkins容器映射的data文件下&#xff09; 点击容器-->管理-->数据存储卷--.把相…

C语言 ——— 函数

目录 函数是什么 库函数 学习使用 strcpy 库函数 自定义函数 写一个函数能找出两个整数中的最大值 写一个函数交换两个整型变量的内容 牛刀小试 写一个函数判断一个整数是否是素数 写一个函数判断某一年是否是闰年 写一个函数&#xff0c;实现一个整型有序数组的二分…

笔记本电脑升级计划(2017———2025)

ThinkPad T470 (2017) vs ThinkBook 16 (2025) 完整性能对比报告 一、核心硬件性能对比 1. CPU性能对比&#xff08;i5-7200U vs Ultra9-285H&#xff09; 参数i5-7200U (2017)Ultra9-285H (2025)提升百分比核心架构2核4线程 (Skylake)16核16线程 (6P8E2LPE)700%核心数制程工…

具身系列——PPO算法实现CartPole游戏(强化学习)

完整代码参考&#xff1a; https://gitee.com/chencib/ailib/blob/master/rl/ppo_cartpole.py 执行结果&#xff1a; 部分训练得分&#xff1a; (sd) D:\Dev\traditional_nn\feiai\test\rl>python ppo_cartpole_v2_succeed.py Ep: 0 | Reward: 23.0 | Running: 2…

Python项目源码60:电影院选票系统1.0(tkinter)

1.功能特点&#xff1a;通常选票系统应该允许用户选择电影、场次、座位&#xff0c;然后显示总价和生成票据。好的&#xff0c;我得先规划一下界面布局。 首先&#xff0c;应该有一个电影选择的列表&#xff0c;可能用下拉菜单Combobox来实现。然后场次时间&#xff0c;可能用…

【全队项目】智能学术海报生成系统PosterGenius--图片布局生成模型LayoutPrompt(2)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;大模型实战训练营_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前…

Linux的时间同步服务器(附加详细实验案例)

一、计时方式的发展 1.古代计时方式​ 公元前约 2000 年&#xff1a;古埃及人利用光线留下的影子计时&#xff0c;他们修建高耸的大型方尖碑&#xff0c;通过追踪方尖碑影子的移动判断时间&#xff0c;这是早期利用自然现象计时的典型方式 。​商朝时期&#xff1a;人们开发并…

【无需docker】mac本地部署dify

环境安装准备 #安装 postgresql13 brew install postgresql13 #使用zsh的在全局添加postgresql命令集 echo export PATH"/usr/local/opt/postgresql13/bin:$PATH" >> ~/.zshrc # 使得zsh的配置修改生效 source ~/.zshrc # 启动postgresql brew services star…

(5)概述 QT 的元对象系统里的类的调用与联系,及访问接口

&#xff08;1&#xff09; QT 的元对象系统&#xff0c;这几个字大家都知道&#xff0c;那么 QT 的元对象系统里都包含哪些内容呢&#xff0c;其访问接口是如何呢&#xff1f; 从 QObject 类的实现里&#xff0c;从其数据成员里就可以看出来&#xff1a; QT 里父容器可以释放其…

打包 Python 项目为 Windows 可执行文件:高效部署指南

Hypackpy 是一款由白月黑羽开发的 Python 项目打包工具&#xff0c;它与 PyInstaller 等传统工具不同&#xff0c;通过直接打包解释器环境和项目代码&#xff0c;并允许开发者修改配置文件以排除不需要的内容&#xff0c;从而创建方便用户一键运行的可执行程序。以下是使用 Hyp…

MySQL JOIN详解:掌握数据关联的核心技能

一、为什么需要JOIN&#xff1f; 在关系型数据库中&#xff0c;数据通常被拆分到不同的表中以提高存储效率。当我们需要从多个表中组合数据时&#xff0c;JOIN操作就成为了最关键的技能。通过本文&#xff0c;您将全面掌握MySQL中7种JOIN操作&#xff0c;并学会如何在实际场景中…

Kdump 收集器及使用方式

以下是 Linux 系统中 Kdump 转储收集器的详细说明及其使用方法&#xff0c;涵盖核心工具、配置方法及实际示例&#xff1a; 一、Kdump 收集器分类及作用 Kdump 的核心功能是通过 捕获内核 生成内存转储文件&#xff08;vmcore&#xff09;&#xff0c;其核心收集器包括&#…

Error: error:0308010C:digital envelope routines::unsupported 高版本node启动低版本项目运行报错

我的问题就是高版本node启动旧版本项目引起的问题&#xff0c;单独在配置 package.json文件中配置并运行就可以&#xff0c;大概意思就是设置node的openssl "scripts": {"dev": "SET NODE_OPTIONS--openssl-legacy-provider && vue-cli-servi…

松下机器人快速入门指南(2025年更新版)

松下机器人快速入门指南&#xff08;2025年更新版&#xff09; 松下机器人以其高精度、稳定性和易用性在工业自动化领域广泛应用。本文将从硬件配置、参数设置、手动操作、编程基础到维护保养&#xff0c;全面讲解松下机器人的快速入门方法&#xff0c;帮助新手快速掌握核心操…