目录

一.堆的概念与结构

1.1 堆的概念

1.2 堆性质: 

1.3 堆的结构定义

 二.堆的初始化和销毁

2.1 堆的初始化:

2.2 堆的销毁:

三.堆的插入数据(含向上调整算法的实现) 

3.1 插入逻辑

3.2 插入函数

3.3 向上调整算法

三. 堆的删除数据(含向下调整算法)

3.1 删除逻辑

3.2 向下调整算法

3.3 删除栈顶

四.堆的取堆顶 

五.堆排序的实现

5.1 借助数据结构实现排序

5.2 真正的堆排序算法


一.堆的概念与结构

堆(Heap)是一种特殊的完全二叉树,同时也是一种高效的数据结构,主要用于实现优先队列等场景。其核心特性是节点之间的数值关系满足严格的堆序性,具体可分为大堆(大顶堆) 和小堆(小顶堆)

1.1 堆的概念

堆的本质
堆是一棵完全二叉树(结构特性),且每个节点的值必须满足:

  • 若为大堆:每个节点的值大于等于其左右子节点的值(根节点是最大值)。
  • 若为小堆:每个节点的值小于等于其左右子节点的值(根节点是最小值)。

1.2 堆性质: 

  • 堆中某个结点的值总是不大于或者不小于其父结点的值
  • 堆总是一颗完全二叉树
  • 堆顶是最值(最大值或最小值)

1.3 堆的结构定义

// 堆的结构体定义(大堆)
typedef int HPDataType;  // 堆中元素的类型
typedef struct Heap {HPDataType* arr;       // 存储堆元素的数组int size;            // 当前堆中元素的个数int capacity;        // 堆的最大容量
} Heap;

 二.堆的初始化和销毁

2.1 堆的初始化:

// 初始化空堆
void HeapInit(Heap* hp) {if (hp == NULL) {return;  // 处理空指针}hp->arr = NULL;hp->size = 0;hp->capacity = 0;
}

2.2 堆的销毁:

销毁之前先检查数组为不为空,不为空就释放掉然后置空,并把size和capacity赋值为0

//销毁
void HPDestory(HP* php)
{assert(php);if (php->arr)free(php->arr);php->arr = NULL;php->size = php->capacity = 0;
}

三.堆的插入数据(含向上调整算法的实现) 

首先我们需要知道插入数据的逻辑 然后通过向上调整的方法来实现

向上调整算法

向上调整(也称 “上浮”)的核心是:新插入的元素从底部逐步向上移动,与父节点比较,若不满足堆序则交换,直到找到合适位置或到达根节点。

3.1 插入逻辑

  1. 空间检查:若堆已满,需扩容(类似动态数组扩容)。
  2. 添加元素:将新元素插入到堆的末尾(数组的最后一个位置)。
  3. 向上调整:从新元素位置开始,与父节点比较并交换,直至满足堆序性(大堆:子节点≤父节点;小堆:子节点≥父节点)。

如下图:

3.2 插入函数

首先插入时 我们需要判断空间是否足够 若不够的话就需要进行增容 代码如下

//往堆里面插入数据
void HPPush(HP* php, HPDataType x)
{//检查空间是否足够//不够就扩容if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;HPDataType* tmp = (HPDataType*)realloc(php->arr,newcapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail!");exit(1);}php->arr = tmp;php->capacity = newcapacity;}//够就直接插入php->arr[php->size++] = x;//向上调整AdjustUp(php->arr, php->size - 1);//因为前面size++了,所有传的是size-1
}

3.3 向上调整算法

//交换
void swap(int* a, int* b)
{int temp = *a;*a = *b;*b = temp;
}
//向上调整
void AdjustUp(HPDataType*arr, int child)
{assert(arr);int parent = (child - 1) / 2;while (child > 0){//大堆:>//小堆:<if (arr[child] > arr[parent]){swap(&arr[child], &arr[parent]);child = parent;parent= (child - 1) / 2;}else{break;}}
}

大堆为例,步骤如下:

  • 设新元素索引为child,其父节点索引为parent = (child - 1) / 2(数组从 0 开始)。
  • a[child] > a[parent](大堆条件),交换两者,更新child = parent,继续向上比较。
  • 重复上述步骤,直到child = 0(根节点)或a[child] ≤ a[parent]

三. 堆的删除数据(含向下调整算法)

堆的删除操作通常指删除堆顶元素(根节点,即最大值或最小值),并通过向下调整算法重新维护堆的特性。以下是具体实现:

3.1 删除逻辑

  1. 替换堆顶:用堆的最后一个元素覆盖堆顶元素(避免直接删除堆顶导致结构破坏)。
  2. 缩减规模:堆的元素数量减 1(逻辑上移除最后一个元素)。
  3. 向下调整:从新的堆顶开始,与左右子节点中符合堆序的节点交换,直至满足堆的特性。

如下图

3.2 向下调整算法

向下调整(也称 “下沉”)的核心是:将替换后的堆顶元素逐步向下移动,与左右子节点中更符合堆序的节点(大堆选较大者,小堆选较小者)交换,直到找到合适位置或成为叶子节点。

大堆为例,步骤如下:

  • 设当前节点索引为parent,左子节点索引为child = 2 * parent + 1(数组从 0 开始)。
  • 若右子节点存在且大于左子节点,更新child为右子节点索引。
  • a[parent] < a[child](大堆条件),交换两者,更新parent = child,继续向下比较。
  • 重复上述步骤,直到child超出堆的范围或a[parent] ≥ a[child]
//向下调整
void AdjustDown(HPDataType* arr, int parent, int n)
{assert(arr);int child = 2 * parent + 1;while (child < n){//child+1也小于n//后面的小堆就是取小的,大堆取大的孩子if (child + 1 < n && arr[child] < arr[child + 1]){child++;}//大堆:>   小堆:<if (arr[child] > arr[parent]){swap(&arr[child], &arr[parent]);parent = child;child = 2 * parent + 1;}else{break;}}
}

3.3 删除栈顶

首先我们需要一个判断是否为空的函数

//判空
bool HPEmpty(HP* php)
{assert(php);return php->size == 0;
}

删除栈顶函数

//删除(堆顶操作)
void HPPop(HP* php)
{assert(!HPEmpty(php));//首尾交换swap(&php->arr[0], &php->arr[php->size - 1]);php->size--;//向下调整AdjustDown(php->arr, 0, php->size);
}

先判断不为空,然后交换首尾,直接--size删掉,再通过向下调整算法把删除一个数据后的堆重新调整成大堆。 

四.堆的取堆顶 

首先判断是否为空 若不为空则直接返回栈顶元素arr[0]

//取堆顶
HPDataType HPTop(HP* php)
{assert(!HPEmpty(php));return php->arr[0];
}

五.堆排序的实现

这里向大家介绍两种方式 但其实第一种并不是真正意义上的排序 只是达到了排序的目的而已

下面介绍第一种

5.1 借助数据结构实现排序

#include"Heap.h"//堆排序--这不是真正的堆排序,而是利用了堆这个数据结构来实现的排序
void HeapSort1(int* arr, int n)
{HP sp;               // 定义一个堆结构体变量HPInit(&sp);         // 初始化堆(创建空堆)// 1. 将数组所有元素插入堆中for (int i = 0; i < n; i++){HPPush(&sp, arr[i]);  // 插入元素,内部会通过向上调整维护堆序}// 2. 从堆中提取元素,重构数组int i = 0;while (!HPEmpty(&sp))    // 当堆不为空时{HPDataType top = HPTop(&sp);  // 获取堆顶元素(最小值或最大值)arr[i++] = top;               // 将堆顶元素存入原数组,依次填充HPPop(&sp);                   // 删除堆顶元素,内部通过向下调整维护堆序}
}
int main()
{int arr[6] = { 30,56,25,15,70,10 };printf("排序之前:\n");for (int i = 0; i < 6; i++){printf("%d ", arr[i]);}printf("\n");HeapSort1(arr, 6);printf("排序之后:\n");for (int i = 0; i < 6; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

核心思路

  1. 借助堆存储元素:将待排序数组的所有元素依次插入堆中(构建堆)。
  2. 提取最值并重构数组:反复从堆顶提取最小值(或最大值),按顺序存入原数组,最终得到有序数组。

为什么说 “这不是真正的堆排序”?

真正的堆排序(如之前实现的HeapSort)是原地排序,直接在原数组上通过构建堆和调整堆实现排序,不需要额外的堆结构,空间复杂度为O(1)

而这段代码的本质是:

  1. 额外创建了一个堆(HP sp),需要O(n)的额外空间存储元素;
  2. 排序过程依赖于堆的插入和删除操作,本质是 “利用堆的特性进行排序”,而非严格意义上的原地堆排序。

5.2 真正的堆排序算法

通过构建大堆并反复提取最大值来实现数组的升序排序。其核心特点是原地排序(无需额外空间存储堆),充分利用堆的特性高效完成排序。以下是详细解释:

一、核心思路

  1. 构建大堆:将待排序数组转换为大堆(每个父节点的值 ≥ 子节点的值),此时堆顶(数组首位)是最大值。

  2. 排序过程

    • 交换堆顶(最大值)与当前堆的最后一个元素,将最大值放到数组末尾(最终位置)。

    • 缩小堆的范围(排除已排好的末尾元素),对新堆顶执行向下调整,重新维护大堆特性。

    • 重复上述步骤,直到所有元素排序完成。

二、代码逐段解析

1. HeapSort 函数(排序核心)

void HeapSort(int* arr, int n)
{// 1. 构建大堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(arr, i, n);  // 从最后一个非叶子节点开始向下调整}// 2. 执行排序int end = n - 1;  // 标记当前堆的最后一个元素索引while (end > 0){// 交换堆顶(最大值)与当前堆的最后一个元素swap(&arr[0], &arr[end]);// 缩小堆范围(end减1),对新堆顶执行向下调整,重建大堆AdjustDown(arr, 0, end);end--;  // 下一次排序的堆范围更小}
}

(1)构建大堆

  • 关键代码for (int i = (n - 1 - 1) / 2; i >= 0; i--)

  • 计算最后一个非叶子节点的索引(n-1-1)/2(等价于 (n-2)/2)。

  • 数组从 0 开始,最后一个元素索引为 n-1,其父亲节点索引为 (n-1-1)/2
  • 从下往上调整:从最后一个非叶子节点开始,依次向上对每个节点执行 AdjustDown(向下调整),最终将整个数组转换为大堆。

  • 为什么从非叶子节点开始?
    叶子节点没有子节点,无需调整;非叶子节点可能破坏堆序,需通过向下调整使其子树满足大堆特性。

(2)排序过程

  • 交换堆顶与末尾元素swap(&arr[0], &arr[end])
    大堆的堆顶(arr[0])是当前堆中的最大值,交换后最大值被放到数组末尾(arr[end]),即它的最终位置。

  • 向下调整重建大堆AdjustDown(arr, 0, end)
    交换后堆顶元素可能破坏堆序,需从堆顶开始向下调整,但此时堆的范围已缩小为 [0, end-1]end 位置已排好序)。

  • 循环缩小范围end--
    每次循环后,已排序的元素增加一个,堆的范围持续缩小,直到 end=0(所有元素排序完成)。

2. main 函数(测试逻辑)

int main()
{int arr[6] = { 30,56,25,15,70,10 };  // 待排序数组printf("排序之前:\n");for (int i = 0; i < 6; i++){printf("%d ", arr[i]);  // 输出:30 56 25 15 70 10}printf("\n");HeapSort(arr, 6);  // 调用堆排序函数printf("排序之后:\n");for (int i = 0; i < 6; i++){printf("%d ", arr[i]);  // 输出:10 15 25 30 56 70(升序)}printf("\n");return 0;
}
  • 测试数组初始为 [30,56,25,15,70,10],经过堆排序后变为升序数组 [10,15,25,30,56,70]

三、关键辅助函数说明

  1. AdjustDown(向下调整算法)
    作用:当某个节点破坏大堆特性时,通过与左右子节点中较大的一个交换,逐步向下移动,直至子树重新满足大堆特性。
    (代码中未显示实现,但核心逻辑是:选择左右子节点的最大值,与父节点比较,不满足大堆则交换并继续调整。)

  2. swap(交换函数)
    作用:交换两个元素的值,用于将堆顶最大值移动到数组末尾。

四、为什么大堆能实现升序排序?

  • 大堆的堆顶始终是当前堆中的最大值,每次将最大值放到数组末尾,相当于 “从后往前” 依次确定最大元素的位置。

  • 经过 n-1 次交换和调整后,数组从前往后依次递增,最终实现升序。

五、算法特性

  • 时间复杂度O(n+logn)(构建堆 O(n) + 排序阶段 O(nlog+n))。

  • 空间复杂度O(1)(原地排序,仅需常数级额外空间)。

  • 不稳定性:交换过程可能改变相等元素的相对顺序(例如 [2, 2, 1] 排序后可能变为 [1, 2, 2],但两个 2 的原始顺序可能改变)。

总结

这段代码是标准的堆排序实现,通过 “构建大堆→交换堆顶与末尾→调整堆” 的循环,高效完成升序排序。其原地排序的特性和 O(n+log n) 的时间复杂度,使其在大规模数据排序中表现优异。

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

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

相关文章

深入解析 Kubernetes 中的 Service 资源:为应用提供稳定的网络访问

什么是 Kubernetes 中的 Service&#xff1f; 在现代微服务架构中&#xff0c;服务之间的通信和负载均衡是至关重要的。尤其是在 Kubernetes 环境中&#xff0c;由于 Pod 是动态创建和销毁的&#xff0c;如何为一组 Pod 提供稳定的访问入口&#xff0c;成为了架构设计中的一个关…

使用Samba网络磁盘作为MacOS时间机器的远程备份磁盘

最近考虑MacOS系统升级&#xff0c;所以需要做磁盘备份&#xff0c;MacOS里有个备份磁盘很方便的工具&#xff1a;时间机器&#xff0c;可以自动定期备份磁盘&#xff0c;但是一般需要一个大点的移动硬盘插在macbook上选择其为备份磁盘&#xff0c;可惜我并没有移动硬盘&#x…

智能头盔实时监控系统设计与实现

智能头盔实时监控系统设计与实现 源码 https://gitee.com/intostars/csdn-demo/tree/master/src/views/smartHelmet 预览 一、功能概述 智能头盔实时监控系统是基于Vue 3和TypeScript开发的一套用于远程监控和控制智能头盔设备的前端应用模块。该系统通过WebSocket与后端服务…

Docker 学习笔记(八):容器运行时工具实践及 OpenStack 部署基础

容器管理工具Containerd nerdctl 实践 nerdctl管理存储 nerdctl命令创建容器的时候&#xff0c;可以使用-v选项将本地目录挂载给容器实现数据持久化 示例&#xff1a; [rootlocalhost ~]# mkdir /data [rootlocalhost ~]# nerdctl run -d -v /data:/data busybox -- sleep infi…

Unity键盘控制角色运动

以下是一个完整的Unity角色移动和跳跃脚本,支持WASD或方向键移动: 使用说明 确保组件设置正确: 确保您的游戏对象有一个CharacterController组件 如果没有,可以通过菜单 "Component -> Physics -> Character Controller" 添加 相机设置: 确保场景中有一…

linux 宏 DEVICE_ATTR

理解 DEVICE_ATTR DEVICE_ATTR 是 Linux 内核中用于创建设备属性的宏&#xff0c;通常用于 sysfs 文件系统。通过 sysfs&#xff0c;用户空间的程序可以读取或修改内核中的设备属性。DEVICE_ATTR 宏定义在 <linux/device.h> 头文件中&#xff0c;用于声明和定义一个设备属…

MCP模型上下文协议以及交互流程

1. MCP 是什么全称&#xff1a;Model Context Protocol定位&#xff1a;让大语言模型&#xff08;LLM&#xff09;能在“上下文”之外&#xff0c;按统一格式访问外部数据、调用插件、持久化状态。动机&#xff1a;以前每家框架&#xff08;LangChain、LlamaIndex 等&#xff0…

MySQLTransactionRollbackException

问题描述mysql部署1主3从&#xff0c;昨天发现主库有大量报警错误&#xff1a;Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction ; Deadlock found when trying to get lock; …

Redis环境搭建指南:Windows/Linux/Docker多场景安装与配置

Redis环境搭建指南&#xff1a;Windows/Linux/Docker多场景安装与配置 1. Redis安装方式概览 1.1 安装方式对比 安装方式适用场景优点缺点难度Windows直接安装开发调试安装简单&#xff0c;Windows兼容好性能不如Linux&#xff0c;生产不推荐⭐Linux源码编译生产环境性能最佳…

leetcode.80删除有序数组中的重复项2

题目描述 给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。…

运动卡新手入门及常见问题处理

1.新手入门1.1 插卡打开包装&#xff0c;拿出PCI板卡&#xff0c;如下图&#xff1a;打开电脑机箱盖&#xff0c;找到PCI插槽&#xff0c;如下图&#xff08;红色框部分是PCI槽&#xff0c;有些主板上PCI槽是白色或其他颜色&#xff09;&#xff1a;插入板卡&#xff0c;如下图…

PRINCE2与PMP项目管理体系对比

在全球范围内&#xff0c;PRINCE2与PMP是两大最具影响力的项目管理体系。PRINCE2注重流程和治理结构&#xff0c;强调“控制”与“规范”&#xff1b;而PMP基于PMBOK指南&#xff0c;强调知识体系和方法论的全面性&#xff0c;更关注“工具”与“实践”。 不同体系的侧重点&…

在UniApp跨平台开发中实现相机自定义滤镜的链式处理架构

以下是进阶方案&#xff1a;架构核心设计分层结构$$Pipeline Capture \otimes Filter_1 \otimes Filter_2 \otimes \cdots \otimes Filter_n \otimes Render$$ 其中&#xff1a;$\otimes$ 表示链式处理操作符$Capture$ 为原始图像采集层$Filter_n$ 为可插拔滤镜单元$Render$ 为…

Mark5 穿越机电调深度解析:设计、选型、控制与实战(下)

TIM_SetCompare3 (TIM1, T0 + T1 + T2); // W+​ break;​ case 3:​ // U - 导通,V - 导通,W + 导通​ TIM_SetCompare1 (TIM1, T0); // U-​ TIM_SetCompare2 (TIM1, T0); // V-​ TIM_SetCompare3 (TIM1, T0 + T1 + T2); // W+​ break;​ case 4:​ // U - 导通…

背包问题从入门到入土

我在这里介绍4种常见的背包问题&#xff0c;这里我想按易 --> 难程度从01背包&#xff0c;完全背包&#xff0c;分组背包&#xff0c;多重背包的顺序介绍。&#xff08;封面附在最后&#xff09;一&#xff0c;01背包问题&#xff08;后面三个背包问题的基础&#xff09;01背…

Leetcode 18 java

​​​​​1​​​​​​​141. 环形链表1 题目 ​​​​​1​​​​​​​141. 环形链表 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表…

Linux 正则表达式详解(基础 + 扩展 + 实操)

Linux 正则表达式详解&#xff08;基础 扩展 实操&#xff09; 正则表达式&#xff08;Regular Expression&#xff0c;简称 RE&#xff09;是 Linux 文本处理的核心工具&#xff0c;用于定义字符匹配模式&#xff0c;配合 grep、sed、awk 等工具可实现文本过滤、查找、替换等…

Json-rpc通信项目(基于C++ Jsoncpp muduo库)

一、介绍RPC RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用&#xff0c;一种通过网络从远程计算器上请求服务&#xff0c;而不需要了解底层网络通信细节&#xff0c;RPC可以使用多种网络协议进行通信&#xff0c;并且在TCP/IP网络四层模型中跨越了传输层和应…

RL【9】:Policy Gradient

系列文章目录 Fundamental Tools RL【1】&#xff1a;Basic Concepts RL【2】&#xff1a;Bellman Equation RL【3】&#xff1a;Bellman Optimality Equation Algorithm RL【4】&#xff1a;Value Iteration and Policy Iteration RL【5】&#xff1a;Monte Carlo Learnin…

Redis是什么?一篇讲透它的定位、特点与应用场景

Redis是什么&#xff1f;一篇讲透它的定位、特点与应用场景 1. Redis的定义与核心概念 1.1 什么是Redis&#xff1f; Redis&#xff08;Remote Dictionary Server&#xff09; 是一个开源的、基于内存的数据结构存储系统&#xff0c;可以用作数据库、缓存和消息代理。Redis由…