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

堆的实现与应用:从基础操作到排序与 Top-K 问题

        堆作为一种特殊的完全二叉树,间距数组的储存效率和二叉树的逻辑特性,是数据结构中解决排序、Top-K等问题的力利器。本文将结合理论原理、完整代码实现和实际应用场景,带大家全面掌握堆的核心知识。

一、堆的核心概念和特性

        堆是满足特定规则的完全二叉树,底层通常采用数组顺序存储,主要分为两种类型:

        小根堆:每个父节点的值小于等于其左右子节点的值,根节点为整个堆的最小值。

        大根堆:每个父节点的值大于等于其左右子节点的值,根节点为整个堆的最大值。

        完全二叉树的关键性质:

        1. 对于下标为 i 的节点,其父节点的下标为(i-1)/2

        2. 对于下标为 i 的节点,其左孩子下标为 2*i+1 ,右孩子下标为 2*i+2

二、堆的核心操作实现

        这里用小根堆来进行演示。

1.数据结构定义

        首先定义堆的结构体,包括存储数据的数组、当前元素个数和容量

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>typedef int HPDataType;
typedef struct Heap 
{HPDataType* a;    // 存储堆元素的数组int size;         // 当前堆中元素个数int capacity;     // 堆的最大容量
} Hp;

2.向上调整算法

        用于堆的插入操作,维持小根堆性质

        1. 新元素插入到数组尾部(堆的最后一个位置)

        2. 不断与父节点比较,若小于父节点则交换,指导满足堆性质或到达根节点

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 (child > 0) {if (a[child] < a[parent]) {  // 小根堆:子节点小于父节点则交换Swap(&(a[child]), &(a[parent]));child = parent;parent = (child - 1) / 2;} else {break;  // 满足堆性质,退出调整}}
}

3. 向下调整算法

        用于堆的删除操作,维持小根堆的性质

        1. 调整的前提是左右子树已满足堆性质,删除堆顶元素

        2. 将堆顶元素与最后一个节点交换位置,不会破坏根节点左右子树的结构

        3. 选择左右子节点中较小的一个,与父节点比较,若子节点更小则交换,递归调整子树。

void AdJustDown(HPDataType* a, int n, int parent) 
{int child = 2 * parent + 1;  // 先假设左子节点为较小的孩子while (child < n) {// 选择左右子节点中较小的一个if (child + 1 < n && a[child] > a[child + 1]) {child++;}// 子节点小于父节点则交换,继续向下调整if (a[child] < a[parent]) {Swap(&(a[child]), &(a[parent]));parent = child;child = 2 * parent + 1;} else {break;  // 满足堆性质,退出调整}}
}

4. 堆的完整操作

(1)初始化与销毁

// 初始化堆
void HeapInit(Hp* hp)
{assert(hp);hp->a = NULL;hp->size = hp->capacity = 0;
}// 销毁堆,释放内存
void HeapDestory(Hp* hp) 
{assert(hp);free(hp->a);hp->a = NULL;hp->capacity = hp->size = 0;
}

(2)插入与删除

// 插入元素到堆
void HeapPush(Hp* hp, HPDataType x) 
{assert(hp);// 容量不足时扩容(初始容量为4,之后翻倍)if (hp->size == hp->capacity) {int newcapacity = hp->capacity == 0 ? 4 : 2 * hp->capacity;hp->a = (HPDataType*)realloc(hp->a, newcapacity * sizeof(HPDataType));hp->capacity = newcapacity;}hp->a[hp->size] = x;  // 尾部插入AdJustUp(hp->a, hp->size);  // 向上调整hp->size++;
}// 删除堆顶元素(堆的核心元素)
void HeapPop(Hp* hp) 
{assert(hp);assert(hp->size > 0);Swap(&(hp->a[0]), &(hp->a[hp->size - 1]));  // 堆顶与最后一个元素交换hp->size--;  // 删除最后一个元素(原堆顶)AdJustDown(hp->a, hp->size, 0);  // 向下调整堆顶
}

(3)查询与判断

// 获取堆顶元素
HPDataType HeapTop(Hp* hp) 
{assert(hp);assert(hp->size > 0);return hp->a[0];
}// 获取堆的大小
int HeapSize(Hp* hp) 
{assert(hp);return hp->size;
}// 判断堆是否为空
bool HeapEmpty(Hp* hp) 
{assert(hp);return hp->size == 0;
}

三、堆的两大核心应用

1.堆排序

        利用堆的性质实现排序,时间复杂度 O (n log n)

        升序排序:构建大根堆,每次将堆顶(最大值)与末尾元素交换,再调整前 n-1 个元素为大根堆。

        降序排序:构建小根堆,逻辑与升序类似。

void HeapSort(int* a, int size) 
{// 1. 构建堆(从最后一个非叶子节点的父节点开始向下调整)for (int i = (size - 1 - 1) / 2; i >= 0; i--) {AdJustDown(a, size, i);}// 2. 排序:逐步将堆顶元素移到末尾int end = size - 1;while (end > 0) {Swap(&(a[0]), &(a[end]));  // 堆顶(最大值)与末尾交换AdJustDown(a, end, 0);     // 调整前end个元素为堆end--;}
}

2.Top-K问题(求前K个最大值)

        对于海量数据(无法全部加载到内存),堆是解决 Top-K 问题的最优方案。

        1. 用数据集合中前 K 个元素来建堆

                (1)前 K 个最大的元素,则建小堆

                (2)前 K 个最小的元素,则建大堆

        2. 用剩余的 N-K 个元素一次于堆顶元素来比较,满足条件就覆盖根,然后调整

        3. 将剩余 N-K 个元素依次与堆顶元素⽐完之后,堆中剩余的K个元素就是所求的前K个最⼩或者最⼤的元素

// 生成海量测试数据(100万个随机数)
void creatdata() 
{int n = 1000000;FILE* file = fopen("data.txt", "w");if (file == NULL) {perror("fopen");return;}srand((unsigned int)time(NULL));for (int i = 0; i < n; i++) {int x = (rand() + i) % 100000;fprintf(file, "%d\n", x);}fclose(file);
}// 求解Top-K问题
void topk() 
{creatdata();  // 生成数据FILE* fout = fopen("data.txt", "r");if (fout == NULL) {perror("fopen");return;}int k = 0;printf("请输入要查找的最大元素个数:");scanf("%d", &k);// 开辟K个元素的数组,存储前K个数据int* arr = (int*)malloc(k * sizeof(int));for (int i = 0; i < k; i++) {fscanf(fout, "%d", &arr[i]);}// 构建小根堆for (int i = (k - 1 - 1) / 2; i >= 0; i--) {AdJustDown(arr, k, i);}// 遍历剩余数据,更新堆int x = 0;while (fscanf(fout, "%d", &x) != EOF) {if (x > arr[0]) {  // 大于堆顶(当前最小值)则替换arr[0] = x;AdJustDown(arr, k, 0);  // 调整堆}}// 输出前K个最大值printf("前%d个最大值为:", k);for (int i = 0; i < k; i++) {printf("%d ", arr[i]);}printf("\n");free(arr);fclose(fout);
}

        要判断上面的代码是否正确,可以在生成数据文件后在 K 个数据后面加上一些数字(不要溢出),他们就是最大的。如果结果中是这些数字,证明是对的。

http://www.dtcms.com/a/614015.html

相关文章:

  • Python应用指南:利用高德地图API获取实时天气
  • 移动网站开发教程网站开发有哪些服务
  • 体彩网站建设校园文化建设网站素材
  • Java绘图技术
  • R语言基础(包含资料)
  • 系统思考:打破惯性
  • 数据结构入门 (十一):“自我平衡”的艺术 —— 详解AVL树
  • 网站建设html东莞浩智网站建设多少钱
  • 【工具】文件传输工具_wget·aria2·ssh·scp
  • Python-PDF文件生成水印
  • 站长之家官网查询电子商务网站开发教程论文
  • openGauss:多核时代企业级数据库的性能与高可用新标杆
  • C++ 中dynamic_cast使用详解和实战示例
  • Git笔记---简单介绍与基本使用
  • php网站开发项目经验如何写wordpress是什么软件
  • 手机网站排名优化软件怎么查网站开发者联系方式
  • 菲律宾有做网站的吗人人开发网站
  • 部署Cloudflare免费图床——免费开源强大
  • Vue Router 3 升级 4:写法、坑点、兼容一次讲透
  • JSP 、JSTL、MVC分层思想——以登录验证为例
  • 新操作系统。
  • Hutool-JSON 工具类超全使用指南:告别 JSON 处理繁琐操作
  • 445 端口(SMB 服务)完整渗透流程总结
  • 咔咔做受视频网站摄影师网站建设
  • 大连建设网网址是多少啊重庆seo网站设计
  • TDengine 字符串函数 POSITION 用户手册
  • 燕郊建设局网站网站排名首页前三位
  • Docker容器使用手册——进阶篇(下)
  • C++入门指南:开启你的编程之旅
  • 智取能量:如何最大化战斗分数?