当前位置: 首页 > ai >正文

数据结构:在堆中插入元素(Inserting In a Heap)

目录

我们的目标和约束

如何满足“结构属性”?

怎样修改被破坏的“堆序属性”?

完善代码

总结与分析


现在我们已经深刻理解了堆的静态结构——它是一个用数组表示的、满足特定属性的完全二叉树。

接下来,我们就来探讨如何向这个结构中添加一个新元素,也就是 insert 操作。

我们的目标和约束

当我们向堆中插入一个新元素时,我们的最终目标是:操作完成后,这个数据结构仍然必须是一个合法的堆。

这意味着,插入操作结束后,必须同时满足堆的两条核心属性:

  1. 结构属性:它必须仍然是一棵完全二叉树。

  2. 堆序属性:所有父节点的值必须仍然大于或等于其子节点的值(以大顶堆为例)。

数据结构:堆(Heap)-CSDN博客

我们必须在这两条约束下,找到插入新元素的位置和方法。


如何满足“结构属性”?

我们先来解决相对容易的“结构属性”。为了在插入一个新节点后,整棵树依然是“完全”的,新节点应该放在哪里?

根据完全二叉树的定义(从上到下、从左到右依次填满),新节点有且仅有一个唯一的位置可以放:就是当前这棵树最后一个节点的下一个位置。

在我们的数组表示法中,这个操作极其简单:

如果当前堆中有 size 个元素(存储在数组的 0size-1 位置),那么这个“下一个位置”就是数组的下标 size

所以,插入操作的第一步,就是把新元素放到数组当前最后一个元素的后面,然后把 size 加一。

让我们把这个初步想法写成代码:

// 接着上次的代码
#include <iostream>// (这里是之前定义的 Heap 结构体和 parent, leftChild, rightChild 函数)
// ...// 向堆中插入一个元素
void insert(Heap* h, int value) {// 预先检查:堆是不是已经满了?if (h->size == h->capacity) {std::cout << "错误:堆已满,无法插入新元素。\n";return;}// --- 第一步:满足结构属性 ---// 将新元素放置在数组的末尾h->data[h->size] = value;// 堆中的元素数量加一h->size++;// ... 接下来该做什么?
}

到这里,我们成功地满足了“结构属性”。但这显然还没完。


怎样修改被破坏的“堆序属性”?

我们虽然保证了树的“形状”是正确的,但“堆序属性”很可能已经被我们破坏了。

举个例子。假设我们原来的堆是这样的:

逻辑结构:

      100/   \70    80

数组表示: [100, 70, 80] (size = 3)

现在,我们要插入一个新值:90

按照第二步的操作,我们把它放在数组末尾: [100, 70, 80, 90] (size = 4)

这对应的新逻辑结构是:

      100/   \70    80/90      <-- 新插入的节点

现在我们来检查“堆序属性”。新节点 90 的父节点是谁?

  • 90 的下标是 3

  • 它的父节点下标是 parent(3) = (3 - 1) / 2 = 1

  • 下标 1 的值是 70

⚠️ 我们发现 90 > 70,也就是 子节点 > 父节点。这严重违反了“堆序属性”!

如何修复?

问题出在子节点比父节点大。最直接、最符合逻辑的修复方法,就是把它们两个交换位置,让大的上去,小的下来。

我们来交换 90 (下标3) 和 70 (下标1): [100, 90, 80, 70]

交换后的逻辑结构:

      100/   \90    80/70

我们再检查一下。90 现在换到了新的位置(下标1),它有了新的父节点 100(下标0)。 90 < 100,满足了堆序属性。并且,被换下来的 70 的父节点是 9070 < 90,也满足。

看起来问题解决了。

我们再考虑一个更极端的情况,假设我们要插入的值是 200 呢?

  1. 初始状态: [100, 70, 80]

  2. 插入 200: [100, 70, 80, 200],结构正确,但 200 > 70,堆序被破坏。

  3. 第一次交换: 200 和它的父节点 70 交换。

[100, 200, 80, 70] 现在 200 的下标是 1。我们必须继续检查它和它的新父节点的关系。

200 的新父节点是 parent(1) = (1 - 1) / 2 = 0,对应的值是 100。 我们发现 200 > 100,堆序仍然是错误的,只不过问题向上移动了一层。

从上面的过程我们可以总结出一个规律:

当一个新节点插入后,它可能会比它的父节点大,于是我们交换它们。

交换后,这个节点来到了新的位置,我们必须重复这个过程:继续拿它和新的父节点比较,如果还是比父节点大,就继续交换。

这个过程就像一个气泡在水中不断上浮一样,我们称这个过程为 “上浮” (Sift Up 或 Bubble Up)

这个“上浮”过程什么时候停止呢❓❓❓

  1. 当这个节点“上浮”到了根节点的位置(它的下标已经是0),没有父节点了,自然就停了。

  2. 或者,在“上浮”的某个过程中,我们发现它已经不比它的父节点大了,那么堆序属性在这一点以及以上部分都得到了满足,也可以停了。


完善代码

现在,我们可以把“上浮”这个逻辑添加到我们的 insert 函数中了。

// 为了代码清晰,我们先写一个交换函数
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}// 完整的 insert 函数
void insert(Heap* h, int value) {// 1. 检查容量if (h->size == h->capacity) {std::cout << "错误:堆已满,无法插入新元素。\n";return;}// 2. 将新元素放置在数组的末尾,并增加 sizeh->data[h->size] = value;int currentIndex = h->size; // 记录新元素的当前位置h->size++;// 3. 执行“上浮”操作,恢复堆序属性// 循环条件:只要当前节点不是根节点,并且它的值比父节点大while (currentIndex > 0 && h->data[currentIndex] > h->data[parent(currentIndex)]) {// 交换当前节点和它的父节点swap(&h->data[currentIndex], &h->data[parent(currentIndex)]);// 将索引更新为父节点的索引,为下一次循环做准备currentIndex = parent(currentIndex);}
}

总结与分析

我们通过一步步的推导,最终完成了 insert 函数。回顾一下整个过程:

  1. 根本目的:插入后,结构必须还是一个合法的堆。

  2. 拆分问题:分别考虑“结构属性”和“堆序属性”。

  3. 解决结构属性:将新元素放在数组末尾,这是唯一解。

  4. 发现新问题:这样做可能会破坏“堆序属性”。

  5. 解决堆序属性:通过“上浮”(不断与父节点比较并交换)操作,自下而上地修复堆序。

时间复杂度分析: 这个“上浮”的过程,最坏情况下是从树的最底层一直“浮”到树的顶端(根节点)。它走过的路径长度就是这棵树的高度。

我们知道,一个包含 N 个节点的完全二叉树,其高度为 O(logN)。 因此,堆的插入操作的时间复杂度是 O(logN)。

这个结果非常理想!它远远优于有序数组的 O(N),也解决了我们最初提出的那个问题:找到一个能够同时“比较快”地查找最大值(对于堆是 O(1))和“比较快”地插入新元素(O(logN))的数据结构。

http://www.xdnf.cn/news/18830.html

相关文章:

  • 深度学习-----详解MNIST手写数字数据集的神经网络实现过程
  • Magicodes.IE.Pdf 生成导出PDF文件 bytes Stream FileStreamResult 下载
  • MYSQL---存储过程
  • 能源行业数据库远程运维安全合规实践:Web化平台的落地经验
  • AI 暗战: 回声室攻击 —— 解锁大模型安全防御的隐秘战场
  • [Sync_ai_vid] 唇形同步评判器 | 图像与视频处理器 | GPU测试
  • 【RabbitWQ】基于 Java 实现轻量级消息队列(二)
  • 使用 ROS2 构建客户端-服务器通信:一个简单的计算器示例
  • 储能变流器学习之MPPT
  • 汽车盲点检测系统的网络安全分析和设计
  • k8s-容器化部署论坛和商城服务
  • 开源 | 推荐一套企业级开源AI人工智能训练推理平台(数算岛):完整代码包含多租户、分布式训练、模型市场、多框架支持、边缘端适配、云边协同协议:
  • PMP项目管理知识点-⑮预测型项目概念辨析
  • Web 自动化测试常用函数实战(一)
  • Unity自定义Inspector面板之使用多选框模拟单选框
  • 测试分类(超详解)
  • vue拖动排序,vue使用 HTML5 的draggable拖放 API实现内容拖并排序,并更新数组数据
  • 基于SpringBoot的社区儿童疫苗接种预约系统设计与实现(代码+数据库+LW)
  • 【高级机器学习】3. Convex Optimisation
  • 无限长直导线周围电场分布的MATLAB
  • 【MATLAB例程】二维平面上的多目标TOA定位,目标和TOA基站的数量、位置可自行设置。附代码下载链接
  • 浅谈Elasticsearch数据写入流程的refresh和flush操作
  • ICDE 2025 | 包含OPTIONAL和UNION表达式的SPARQL查询的高效执行方法
  • 硬件开发_基于物联网的儿童座椅系统
  • 3.【鸿蒙应用开发实战: 从入门到精通】开发入门 Hello World
  • 7、prefix-tuning、P-tuning、Prompt-tuning
  • 基于数据安全的旅游民宿租赁系统
  • 音频时长裁剪工具:高效处理音频,让内容创作更轻松
  • docker 所有常用命令,配上思维导图,加图表显示
  • 配送算法16 A Deep Reinforcement Learning Approach for the Meal Delivery Problem