news 2026/7/18 8:05:29

【数据结构】堆的原理与C语言实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【数据结构】堆的原理与C语言实现

目录

一、堆的概念及结构

二、堆的实现

1.堆的向下调整算法

2.堆的创建

3.堆的插入

4.堆的删除

5.堆的代码实现(完整代码)

三、堆的应用

1.堆排序

2.TOP-K问题


一、堆的概念及结构

如果有一个关键码的集合K = {k1,k2,……,kn-1},把它的所有元素按照完全二叉树的顺序存储方式存储在一个一维数组中,并满足Ki<=K(2*i+1)且Ki<=K(2i+2)(或Ki>=K(2*i+1)且Ki>=K(2i+2)),I=1,0,2,……则称为小堆(大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值。
  • 堆总是一棵完全二叉树

二、堆的实现

1.堆的向下调整算法

向下调整算法有一个前提:左右子树必须是一个堆才能调整。

由于根节点的左右子树为空,因此根节点本身就可以看作是一个堆,所以我们可以从完全二叉树的最后一个非叶子节点开始向下调整算法。

如果建小堆,根节点比左右子树大时将根节点和左右子树的根节点中的较小值交换位置,再检查左右子树是否仍符合堆的性质,不符合就继续使用向下调整算法,直到完全符合堆的性质。

// 向下调整算法 void AdjustDown(HPDataType* a, int n, int parent);
void AdjustDown(HPDataType* a, int n, int parent) { // 先假设左孩子小 int child = parent * 2 + 1; while (child < n) { // 如果右孩子比左孩子小,child+1 if (child + 1 < n && a[child] > a[child + 1]) { ++child; } if (a[child] < a[parent]) { Swap(&a[child], &a[parent]); parent = child; child = parent * 2 + 1; } else { break; } } }

传入数组、数组大小和要进行调整的结点。使用假设法找出左右子树根节点的较小值,如果根节点较小值小于其父节点,则进行交换。交换之后将孩子节点看作新的父节点,再算出新的父节点的孩子节点,重复这一过程直到全部变为堆。

2.堆的创建

给出一个数组,这个数组逻辑上可以看作是一个完全二叉树,但还不是一个堆。通过向下调整算法可以将它构建成一个堆。从倒数第一个非叶子结点开始使用向下调整算法,一直调整到根节点,就可以构建成堆。

3.堆的插入

先插入一个数到数组的尾上,再进行向上调整算法,直到满足堆。

如果是小堆,插入一个元素后,和其父节点比较,如果新插入的结点小于父节点,则交换位置,直到二叉树完全满足堆。

// 向上调整算法 void AdjustUP(HPDataType* a, int child); // 堆的插入 void HPPush(HP* php, HPDataType x);
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; } } } 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 (php == 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 HPPop(HP* php);
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); }

5.堆的代码实现(完整代码)

#pragma once #include<stdio.h> #include<assert.h> #include<stdlib.h> #include<stdbool.h> typedef int HPDataType; typedef struct Heap { HPDataType* a; int size; int capacity; }HP; void Swap(HPDataType* p1, HPDataType* p2); // 向上调整算法 void AdjustUP(HPDataType* a, int child); // 向下调整算法 void AdjustDown(HPDataType* a, int n, int parent); // 初始化 void HPInit(HP* php); // 销毁 void HPDestroy(HP* php); // 插入 void HPPush(HP* php, HPDataType x); // 删除 void HPPop(HP* php); // 获取堆顶元素 HPDataType HPTop(HP* php); // 判断堆是否为空 bool HPEmpty(HP* php);
#define _CRT_SECURE_NO_WARNINGS 1 #include"heap.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 (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 (php == 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+1 if (child + 1 < n && a[child] > a[child + 1]) { ++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->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); }

三、堆的应用

1.堆排序

堆排序即利用堆的思想进行排序,分为两个步骤

(1)建堆

  • 升序:建大堆
  • 降序:建小堆

(2)利用堆的删除思想进行排序

建堆和堆删除都用到了向下调整,因此掌握了向下调整就可以完成堆排序。

降序排序为例,排序过程:先将数组建成小堆,再将堆顶数据和最后一个数据交换,然后将堆中的数据个数-1(即将最后一个元素排除出堆),再对堆顶进行向下调整算法,直到二叉树符合堆的性质,重复这一步骤直到堆中只剩一个元素,即为完成了堆排序。

2.TOP-K问题

TOP-K问题:即求数据中前K个最大的元素或最小的元素,一般情况下数据量都比较大(N个数中找最大或最小的前K个数,N远大于K)。

如果数据量很大,排序就不太可能了(数据可能无法一次性加载到内存中)。最佳的方式就是使用堆。基本思路如下:

(1)用数据中前K个元素来建堆

  • 前K个最大的元素则建小堆
  • 前K个最小的元素则建大堆

(2)用剩余的N-K个元素依次与堆顶元素进行比较,不满足则替换堆顶元素。

将剩余的N-K个元素全部比较完成后,堆中剩余的元素就是前k个最大或最小的元素。

void Test() { int a[] = { 4,2,8,1,5,6,9,7 }; HP hp; HPInit(&hp); for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) { HPPush(&hp, a[i]); } // 找出最小的前k个 int k; scanf("%d", &k); while (k--) { printf("%d ", HPTop(&hp)); HPPop(&hp); } }

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/18 8:05:24

Stagehand框架:为AI智能体赋予浏览器自动化能力

1. 项目概述&#xff1a;当AI智能体需要“手”和“眼” 最近在折腾AI智能体&#xff08;Agent&#xff09;项目时&#xff0c;我遇到了一个非常典型的问题&#xff1a;我的智能体能说会道&#xff0c;逻辑清晰&#xff0c;能调用各种API处理结构化数据&#xff0c;但一旦涉及到…

作者头像 李华
网站建设 2026/7/18 8:03:15

Cursor、Claude Code与Codex:2026年AI编程工具的三层分工真相

1. 为什么2026年选AI编程工具不能再只看“谁更聪明” 2026年春天&#xff0c;我给一个做金融风控系统的团队做技术咨询。他们刚花三万块买了某款号称“最强AI编程助手”的年度订阅&#xff0c;结果上线两周就集体停用——不是因为模型不给力&#xff0c;而是它每次生成代码都要…

作者头像 李华
网站建设 2026/7/18 8:02:23

5个理由为什么选择ScreenPipe:终极屏幕记忆与AI自动化解决方案

5个理由为什么选择ScreenPipe&#xff1a;终极屏幕记忆与AI自动化解决方案 【免费下载链接】screenpipe YC (S26) | Record how you work and turn that into agents. Local, private, secure. Connect to OpenClaw, Hermes agent and 100 apps 项目地址: https://gitcode.co…

作者头像 李华
网站建设 2026/7/18 8:02:19

Vosk离线语音识别架构深度解析与企业级实战应用指南

Vosk离线语音识别架构深度解析与企业级实战应用指南 【免费下载链接】vosk-api Offline speech recognition API for Android, iOS, Raspberry Pi and servers with Python, Java, C# and Node 项目地址: https://gitcode.com/GitHub_Trending/vo/vosk-api Vosk离线语音…

作者头像 李华
网站建设 2026/7/18 8:02:17

Unity游戏实时翻译插件XUnity.AutoTranslator:从原理到实战应用

1. 项目概述&#xff1a;XUnity.AutoTranslator 是什么&#xff1f; 如果你是一名喜欢玩各种独立游戏、视觉小说或者日系RPG的玩家&#xff0c;肯定遇到过这样的烦恼&#xff1a;游戏本身质量很高&#xff0c;但偏偏没有官方中文&#xff0c;或者官方翻译质量一言难尽。传统的汉…

作者头像 李华
网站建设 2026/7/18 8:02:10

Java开发电脑配置指南:从入门到架构师

1. Java开发电脑选择的核心考量作为一名有十年Java开发经验的工程师&#xff0c;我经常被问到"什么样的电脑适合Java开发"这个问题。很多人以为随便一台笔记本就能跑Java代码&#xff0c;但实际开发中&#xff0c;IDE、构建工具、测试环境、Docker容器等资源消耗远超…

作者头像 李华