发布时间:2026/8/18 17:59:38
数据结构--顺序结构二叉树(堆) 目录1. 二叉树1.1 概念与结构1.2 特殊的二叉树1.2.1 满二叉树1.2.2 完全二叉树1.2.3 二叉树性质1.3 二叉树存储结构1.3.1 顺序结构1.3.2 链式结构2. 实现顺序结构二叉树2.1 堆的概念与结构2.2 堆的实现2.2.1 代码解析1. 插入操作以小堆为例2. Pop删除3. 向上调整算法4. 向下调整算法5. test1测试样例2.2.2 代码编写Heap.h 头文件Heap.c 源文件test.c 源文件3.堆的应用3.1 堆排序3.2 Top-k 问题正文开始1. 二叉树1.1 概念与结构在树形结构中我们最常用的就是二叉树一棵二叉树是结点的一个有限集合该集合由一个根结点加上两棵别称为左子树和右子树的二叉树组成或者为空。从上图可以看出二叉树具备以下特点二叉树不存在度大于2的结点二叉树的子树有左右之分次序不能颠倒因此二叉树是有序树注意对于任意的二叉树都是由以下几种情况复合而成的1.2 特殊的二叉树1.2.1 满二叉树一个二叉树如果每一个层的结点数都达到最大值则这个二叉树就是满二叉树。也就是说如果一个二叉树的层数为k且结点总数是2^k−12的k次方 -1则它就是满二叉树。1.2.2 完全二叉树完全二叉树是效率很高的数据结构完全二叉树是由满二叉树而引出来的。对于深度为K的有n个结点的二叉树当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。要注意的是满二叉树是一种特殊的完全二叉树。1.2.3 二叉树性质根据满二叉树的特点可知若规定根结点的层数为1则一棵非空二叉树的第 i层上最多有 2的(i-1)次方个结点若规定根结点的层数为1则深度为h的二叉树的最大结点数是 2的h次方-1若规定根结点的层数为1具有n个结点的满二叉树的深度hlog2(n1)(log以2为底n1为对数)1.3 二叉树存储结构二叉树一般可以使用两种结构存储一种顺序结构一种链式结构。1.3.1 顺序结构顺序结构存储就是使用数组来存储一般使用数组只适合表示完全二叉树因为不是完全二叉树会有空间的浪费完全二叉树更适合使用顺序结构存储。现实中我们通常把堆一种二叉树使用顺序结构的数组来存储需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事一个是数据结构一个是操作系统中管理内存的一块区域分段。1.3.2 链式结构二叉树的链式存储结构是指用链表来表示一棵二叉树即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成数据域和左右指针域左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。链式结构又分为二叉链和三叉链当前只介绍二叉链。2. 实现顺序结构二叉树一般堆使用顺序结构的数组来存储数据堆是一种特殊的二叉树具有二叉树的特性的同时还具备其他的特性。堆在物理上来说是一个数组在逻辑上来说是一个顺序结构的二叉树。2.1 堆的概念与结构如果有一个关键码的集合K{k0,k1,k2,...kn−1}把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中并满足KiK2∗i1KiK2∗i1且KiK2∗i2i 0、1、2 ...则称为小堆(或大堆)。将根结点最大的堆叫做最大堆或大根堆根结点最小的堆叫做最小堆或小根堆。堆具有以下性质堆中某个结点的值总是不大于或不小于其父结点的值堆中的兄弟节点间没有大小关系堆总是一棵完全二叉树。对于具有n个结点的完全二叉树如果按照从上至下从左至右的数组顺序对所有结点从0开始编号则对于序号为i的结点有若i0i位置结点的双亲序号(i-1)/2i 0i为根结点编号无双亲结点若2i1左孩子序号2i12i1n否则无左孩子若2i2右孩子序号2i22i2n否则无右孩子2.2 堆的实现堆的底层结构为数组2.2.1 代码解析1. 插入操作以小堆为例若以上述插入10数据为例插入后再经判断后此堆有不符合小堆定义的情况则需要向上调整10这个数据的位置调整过程涉及数据比较、数据交换。如AdjustUp()函数所示。2. Pop删除要求删除堆顶的数据根位置注意不能选择暴力的直接删除堆顶数据的方式因为这样的删除操作会打乱堆的基本结构。正确的做法是将堆顶的数据和堆末尾的数据互换互换之后进行堆末尾的删除然后再利用向下调整函数AdjustDown()将堆调整为正确的结构实现Pop的正确操作3. 向上调整算法将新数据插入到数组的尾上再进行向上调整算法直到满足堆。先将元素插入到堆的末尾,即最后一个孩子之后插入之后如果堆的性质遭到破坏将新插入结点顺着其双双亲往上调整到合适位置即可void AdjustUp(HPDataType* a, int child) { int parent (child - 1) / 2; while (child 0) { if (a[child] a[parent]) { Swap(a[child], a[parent]); child parent; parent (parent - 1) / 2; } else { break; } } } void HPPush(HP* php, HPDataType x) { assert(php); if (php-size php-capacity) { newCapacity php-capacity 0 ? 4 : php-capacity * 2; HPDataType* tmp realloc(php-a, (HPDataType)*newCapacity); if (tmp NULL) { perror(realloc fail); return; } php-a tmp; php-capacity newCapacity; } php-a[php-size] x; php-size; AdjustUp(php-a, php-size - 1); }4. 向下调整算法删除堆是删除堆顶的数据将堆顶的数据根最后一个数据一换然后删除数组最后一个数据再进行向下调整算法。向下调整算法有一个前提左右子树必须是一个堆才能调整。将堆顶元素与堆中最后一个元素进行交换删除堆中最后一个元素将堆顶元素向下调整到满足堆特性为止void AdjustDown(HPDataType* a, int n, int parent) { int child parent * 2 1; while (child n) { //假设法选出左右孩子中小的那个孩子 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; } } } void HPPop(HP* php) { assert(php); assert(php-size 0); Swap(php-a[0], php-a[php-size - 1]); php-size--; AdjustDown(php-a, php-siz, 0); }5. test1测试样例注若想调整小堆调整为大堆只需调整向上调整算法和向下调整算法中的比较逻辑即可此处不做过多解释。2.2.2 代码编写Heap.h 头文件#pragma once #define _CRT_SECURE_NO_WARNINGS 1 #includestdio.h #includeassert.h #includestdlib.h #includestdbool.h typedef int HPDataType; typedef struct Heap { HPDataType* a; int size; int capacity; }HP; void HPInit(HP* php); void HPDestroy(HP* php); void Swap(HPDataType* p1, HPDataType* p2); void AdjustUp(HPDataType* a, int child); void AdjustDown(HPDataType* a, int n, int parent); void HPPush(HP* php, HPDataType x); void HPPop(HP* php); HPDataType HPTop(HP* php); bool HPEmpty(HP* php);Heap.c 源文件#define _CRT_SECURE_NO_WARNINGS #includeHeap.h void HPInit(HP* php) { assert(php); php-a NULL; php-size php-capacity 0; } void HPDestroy(HP* php) { assert(php); free(php-a); php-a NULL; php-size php-capacity 0; } void Swap(HPDataType* p1, HPDataType* p2) { HPDataType tmp *p1; *p1 *p2; *p2 tmp; } void AdjustUp(HPDataType* a, int child) { // 初始条件 // 中间过程 // 结束条件 int parent (child - 1) / 2; //while (parent 0) while (child 0) { if (a[child] a[parent]) { Swap(a[child], a[parent]); child parent; parent (child - 1) / 2; } else { break; } } } void HPPush(HP* php, HPDataType x) { assert(php); if (php-size php-capacity) { int newcapacity php-capacity 0 ? 4 : php-capacity * 2; HPDataType* tmp (HPDataType*)realloc(php-a, newcapacity * sizeof(HPDataType)); if (tmp NULL) { perror(realloc fail); return; } php-a tmp; php-capacity newcapacity; } php-a[php-size] x; php-size; AdjustUp(php-a, php-size - 1); } void AdjustDown(HPDataType* a, int n, int parent) { // 先假设左孩子小 int child parent * 2 1; while (child n) // child n说明孩子不存在调整到叶子了 { // 找出小的那个孩子 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; } } } //最坏的情况下时间复杂度为logN void HPPop(HP* php) { assert(php); assert(php-size 0); Swap(php-a[0], php-a[php-size - 1]); php-size--; AdjustDown(php-a, php-size, 0); } HPDataType HPTop(HP* php) { assert(php); assert(php-size 0); return php-a[0]; } bool HPEmpty(HP* php) { assert(php); return php-size 0; }test.c 源文件#define _CRT_SECURE_NO_WARNINGS #include Heap.h void test1() { int a[] { 4,2,8,1,5,6,9,7 }; HP hp; HPInit(hp); for (size_t i 0; i sizeof(a) / sizeof(int); i) { HPPush(hp, a[i]); } } void test2() { int a[] { 4,2,8,1,5,6,9,7,3,2,23,55,232,66,222,33,7,1,66,3333,999 }; HP hp; HPInit(hp); for (size_t i 0; i sizeof(a) / sizeof(int); i) { HPPush(hp, a[i]); } int i 0; while (!HPEmpty(hp)) { printf(%d , HPTop(hp)); HPPop(hp); } printf(\n); HPDestroy(hp); } void test3() { int a[] { 4,2,8,1,5,6,9,7,3,2,23,55,232,66,222,33,7,1,66,3333,999 }; HP hp; HPInit(hp); for (size_t i 0; i sizeof(a) / sizeof(int); i) { HPPush(hp, a[i]); } //找出最小的前k个 int k 0; scanf(%d, k); while (k--) { printf(%d , HPTop(hp)); HPPop(hp); } printf(\n); HPDestroy(hp); } int main() { //test1(); //小堆的数据插入测试 //test2(); //将小堆里的数据以升序的形式打印显示出来 //test3(); //找出最小的前k个测试该测试时间复杂度为O() return 0; }3.堆的应用3.1 堆排序相较于冒泡排序其时间复杂度为O( N² )堆排序更具实际意义其时间复杂度为O(N * LogN)。版本一基于已有数组建堆、取堆顶元素完成排序版本。但有个前提必须提供有现成的数据结构堆//需要堆的数据结构 //空间复杂度为O(N) void HeapSort(int* a, int n) { HP hp; for (int i 0; i n; i) { HPPush(hp, a[i]); } int i 0; while (!HPEmpty(hp)) { a[i] HPTop(hp); HPPop(hp); } HPDestroy(hp); }版本二数组建堆首尾交换交换后的堆尾数据从堆中删掉将堆顶数据向下调整选出次大的数据。//升序建大堆 //降序建小堆 //O(N*logN) void HeapSort(int* a, int n) { //a数组直接建堆o(N) for (int i (n - 1 - 1) / 2; i 0; --i) { AdjustDown(a, n, i); //向下调整建堆法 } //O(N*logN) int end n - 1; while (end 0) { Swap(a[0], a[end]); AdjustDown(a, end, 0); --end; } }向下建堆法说明此建堆法采取的是从倒数第一个非叶子节点开始往上依次进行向下调整算法的方式进行建堆。即下图中数字5的位置i (n-1-1/2)开始调整。此建堆法时间复杂度为O(N)3.2 Top-k 问题Top-k 问题即求数据结合中前K个最大的元素或者最小的元素一般情况下数据量都比较大。比如专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。对于Top-K问题能想到的最简单直接的方式就是排序但是如果数据量非常大排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决基本思路如下第一步用数据集合中的前k个来建堆需要获取数据中的前k个最大的元素则建小堆需要获取数据中的前k个最小的元素则建大堆第二步用剩余的N-K个元素依次与堆顶元素来比较不满足则替换堆顶元素将剩余N-K个元素依次与堆顶元素比完之后堆中剩余的K个元素就是所求的前K个最小或者最大的元素void CreateNData() { //造数据 int n 100000; srand(time(0)); const char* file data.txt; FILE* fin fopen(file, w); if (fin NULL) { perror(fopen fail); return; } for (int i 0; i n; i) { int x (rand() i) % 1000000; fprintf(fin, %d\n, x); } fclose(fin); } void topk() { printf(请输入k: ); int k 0; scanf(%d, k); const char* file data.txt; FILE* fout fopen(file, r); if (fout NULL) { perror(fopen fail); return; } int val 0; int* minheap (int*)malloc(sizeof(int) * k); if (minheap NULL) { perror(malloc fail); return; } for (int i 0; i k; i) { fscanf(fout, %d, minheap[i]); } //建k个数据的小堆 for (int i (k - 1 - 1) / 2; i 0; i--) { AdjustDown(minheap, k, i); } int x 0; while (fscanf(fout, %d, x) ! EOF) { //读取剩余数据比堆顶的值大就替换他进堆 if (x minheap[0]) { minheap[0] x; AdjustDown(minheap, k, 0); } } for (int i 0; i k; i) { printf(%d , minheap[i]); } fclose(fout); }

相关新闻

2026/8/18 17:54:37

[TypeScript学习笔记-03]常用类型解读

1. 三种原生类型(primitives) JavaScript 中有三种非常常用的基本类型:字符串、数字和布尔值。每种类型在 TypeScript 中都有对应的类型。正如你所预料的,如果你对这些类型的值使用 JavaScript 的 typeof 运算符,你会看…

2026/8/18 19:09:44

窗口尺寸调整工具实测:一招治服拖不动的顽固窗口

窗口尺寸调整工具实测:一招治服拖不动的顽固窗口 【免费下载链接】WindowResizer 一个可以强制调整应用程序窗口大小的工具 项目地址: https://gitcode.com/gh_mirrors/wi/WindowResizer 说个前几天的事:同事要在一个老旧的工单系统里核对数据&am…

2026/8/18 19:09:44

点播视频站验收:播放域、CDN 与 API 三域测速

点播视频站验收:播放域、CDN 与 API 三域测速工具地址:https://www.speedce.com 社区论坛:https://bbs.speedce.com 联系:speedceadsgmail.com写在前面 本文围绕「点播视频站验收」展开,提供可落地的技术方案&#xff…

2026/8/18 19:09:44

1.从零开始的单片机生活-LED篇

目录 1.前言2.51单片机介绍3.LED模块介绍4.点亮一个LED5.LED闪烁6.LED流水灯7.独立按键控制LED亮灭8.独立按键控制LED状态9.独立按键控制LED显示二进制10. 独立按键控制LED移位11.后记 1.前言 《关于没洗杯子在便利店门口穿越异世界学习51单片机这回事》, 分享学习…

2026/8/18 19:09:44

企业级RAG性能优化与质量治理(8):在线反馈、失败样本回流与持续优化闭环

文章摘要 前七篇已经完成文档解析、混合召回、索引版本治理、质量评测、查询增强、可信证据和缓存一致性。系统已经具备较完整的离线工程能力,但只依赖上线前数据集仍然不够。真实用户会带来开发团队从未考虑的口语表达、长文档、权限组合、动态数据、多轮指代、缓存…

2026/8/18 19:04:43

小红书作品怎么下载?免费开源工具三步搞定无水印批量下载

小红书作品怎么下载?免费开源工具三步搞定无水印批量下载 【免费下载链接】XHS-Downloader 小红书(XiaoHongShu、RedNote)链接提取/作品采集工具:提取账号发布、收藏、点赞、专辑作品链接;提取搜索结果作品、用户链接&…

2026/8/17 10:49:52

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/18 6:58:27

工业传感器与变送器详解:序章 从物理世界到工业数据

序章 从物理世界到工业数据 ——重新认识工业传感器与变送器 工业自动化系统正变得日益复杂。今天的工业现场早已不是简单的控制回路,而是由多层技术共同构成的立体体系:PLC、DCS、SCADA、MES、工业互联网、边缘计算与人工智能。控制系统可以执行复杂算法,工业网络可以实现…

2026/8/18 0:02:05

Qwen3.8-27B本地部署实战:17GB内存运行270亿参数大模型

1. 这篇文章真正要解决的问题 你是否曾对动辄需要上百GB显存才能运行的百亿参数大模型望而却步?是否觉得在个人电脑上部署一个功能强大的语言模型是天方夜谭?最近,通义千问团队发布的 Qwen3.8-27B 模型,宣称仅需 17GB 内存即可在本…

2026/8/18 0:02:05

ME3169 36V,8A,180KHz 恒压Buck DC-DC 转换器

概述ME3169 是一款180KHz,PWM 模式恒压Buck DC-DC 转换器,8V 到36V 宽工作电压范围,低纹波,内置低导通电阻功率MOS。ME3169 内置环路补偿电路,可以减少外围元器件数量。内部设计有恒压环路,可以通过外部电阻…

2026/8/18 18:23:10

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/17 17:27:06

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/18 7:12:40

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…