九大排序算法之堆排序

九大排序算法之堆排序

排序算法:堆排序(HeapSort)

关键定义:二叉堆,不稳定的选择排序算法

定义:堆排序 标准定义

堆排序(Heap Sort)是基于二叉堆数据结构实现的一种原地、不稳定的选择排序算法,其核心是利用堆 “父节点与子节点间的大小约束特性”,通过构建初始堆反复提取堆顶极值 + 重新堆化的操作,将无序序列逐步转换为有序序列。

一 ,前置知识点:

1 二叉堆

堆是一个完全二叉树,主要存在两种类型:

大顶堆:每个父节点的值 其左右子节点的值,堆顶(根节点)是整个堆的最大值

小顶堆:每个父节点的值 其左右子节点的值,堆顶是整个堆的最小值

注:采用大顶堆则堆排序为升序,采用小顶堆位降序

2 链表数组这两种线性存储结构

堆排序中对于元素的储存大多采用数组结构,原因如下:

特性数组链表(单链表)
存储结构连续内存空间非连续节点 + 指针串联
访问方式随机访问(O (1))顺序访问(O (n))
插入 / 删除(中间)O (n)(需移动元素)O (1)(仅改指针,找前驱 O (n))
长度特性静态固定(动态数组需扩容)动态可变
内存开销仅存数据,开销小数据 + 指针,额外开销大
缓存友好性优(连续预加载)差(分散无预加载)
内存分配要求需连续大空间,要求高小块分散空间,要求低
代码实现复杂度简单较高(需处理指针)

结构先天适配:堆是完全二叉树,节点排列有规律,能和数组的连续索引一一对应,通过简单公式即可定位父 / 子节点,这是数组成为堆最优存储结构的根本原因;

效率完美匹配:数组的 O (1) 节点定位、缓存友好、原地存储特性,让堆排序的核心操作(堆化、构建堆、堆顶交换)保持高效,确保堆排序 O (n log n) 的时间复杂度和 O (1) 的空间复杂度;

短板完全规避,优势充分发挥:数组的通用缺点(连续内存、静态长度)在堆排序的固定数据量、无动态增删场景下可忽略,而链表的核心优点(动态增删、内存灵活)在堆排序中无意义,核心短板(定位慢、开销大、缓存差)却会直接让堆排序的效率大幅下降,甚至丧失算法优势。

二,堆排序实现过程(数组实现)

假设有如下二叉树

步骤 1:确定堆化的起始节点(最后一个非叶子节点)

构建初始大顶堆时,无需对叶子节点执行堆化(叶子节点无左右子节点,本身已满足堆性质),只需从最后一个非叶子节点开始,从后向前依次对每个节点执行「向下堆化」操作。

最后一个非叶子节点索引计算

数组最后一个元素的索引为n-1,其父节点即为最后一个非叶子节点,索引公式为 (n-1 - 1) // 2 = (n-2) // 2

示例计算:n=11,最后一个非叶子节点索引 = (11-2)//2 = 4,即数组中索引 0~4 为非叶子节点,5~10 为叶子节点,堆化仅需处理索引 {0,1,2,3,4},且从最大索引 4 开始向前执行。

步骤 2:构建初始大顶堆(核心:向下堆化)

向下堆化定义:对指定节点,从该节点向叶子节点遍历,将其与「左右子节点中的最大值」比较,若当前节点小于最大值则交换,交换后继续对新位置的节点执行堆化,直到当前节点≥所有子节点(满足大顶堆性质)或到达叶子节点。

构建逻辑:从最后一个非叶子节点(示例索引 4)开始,向前遍历至根节点(索引 0),对每个节点依次执行向下堆化,最终让整个数组满足大顶堆性质。

部分示例构建过程(数组 {20,30,90,40,70,110,60,10,100,50,80})

堆化索引 3(值 40):左右子节点为索引 7(10)、8(100),最大值为 100(索引 8),40<100,交换后索引 3 值为 100、索引 8 值为 40,该节点堆化完成;

堆化索引 4(值 70):左右子节点为索引 9(50)、10(80),最大值为 80(索引 10),70<80,交换后索引 4 值为 80、索引 10 值为 70,该节点堆化完成;

构建结果:原数组转换为大顶堆{110,100,90,40,80,20,60,10,30,50,70},堆顶(索引 0)为整个数组的最大值 110,满足「所有父节点≥子节点」的大顶堆性质。

补充::必须满足大顶堆性质,完成交换后会破坏之前排序顺序,需要重新检测排序,如下图的a[3]

步骤 3:提取堆顶极值并重新堆化(核心排序过程)

初始大顶堆构建完成后,堆顶为当前无序序列的最大值,通过「堆顶与无序序列最后一个元素交换」,将最大值放到最终有序位置,随后缩小无序序列范围,对新堆顶执行向下堆化,恢复大顶堆性质。重复此操作,直到无序序列长度为 1,整个数组即完成升序排序。

核心规则

  • 设当前无序序列的长度为len(初始 len=n),堆顶为索引 0,无序序列最后一个元素为索引len-1
  • 交换后,索引len-1为有序元素,无序序列长度更新为len-1,仅对新堆顶(索引 0)执行向下堆化即可恢复堆性质(其余节点仍满足堆约束)。

示例排序过程(初始大顶堆 {110,100,90,40,80,20,60,10,30,50,70},n=11)

  1. 第二次操作(len=10):交换堆顶 100(索引 0)与无序序列最后一个元素 50(索引 9),数组变为{50,80,90,40,70,20,60,10,30,100,110},有序元素为 100、110(索引 9-10);无序序列长度更新为 9,堆化后恢复大顶堆为{90,80,60,40,70,20,50,10,30,100,110}
  2. 重复上述操作:每次交换堆顶与当前无序序列最后一个元素,缩小无序序列范围并重新堆化,直到无序序列长度 len=1;
  3. 最终结果:所有元素完成有序排列,数组变为{10,20,30,40,50,60,70,80,90,100,110},升序排序完成。

第一次操作(len=11):交换堆顶 110(索引 0)与无序序列最后一个元素 70(索引 10),数组变为{70,100,90,40,80,20,60,10,30,50,110},有序元素为 110(索引 10);无序序列长度更新为 10,对索引 0 执行向下堆化,恢复大顶堆为{100,80,90,40,70,20,60,10,30,50,110}

三、堆排序的核心特性

1 时间复杂度
  • 构建初始堆:时间复杂度为O(n)(非 O (nlogn),因叶子节点无需堆化,上层节点堆化次数少,整体累计为线性时间);
  • 提取堆顶并堆化:共执行 n-1 次,每次堆化的时间复杂度为 O (logn)(堆的高度为 log₂n+1),总时间复杂度为 O (nlogn);
  • 整体时间复杂度:O(n) + O(nlogn) = O(nlogn),且最好、最坏、平均情况均为 O (nlogn),是少数时间复杂度稳定的高效排序算法。
2 空间复杂度

堆排序的所有操作均在原数组上完成,仅使用常数个临时变量(如索引、交换临时值),无额外的动态内存分配,空间复杂度为 O (1),属于原地排序算法

3 稳定性

堆排序是不稳定排序算法,原因是堆化过程中的「跨位置交换」会破坏相同值元素的相对顺序。

示例:数组{5,3,5,1}构建大顶堆后为{5,3,5,1},第一次交换堆顶 5(索引 0)与最后一个元素 1(索引 3),数组变为{1,3,5,5},原数组中第一个 5(索引 0)与第二个 5(索引 2)的相对顺序被改变。

4 适用场景
  1. 适合大规模数据排序:10 万、100 万级别的数据排序效率远高于冒泡、选择、插入等简单排序,与快速排序、归并排序处于同一高效梯队;
  2. 适合内存有限的场景:原地排序 O (1) 的空间复杂度,无需额外的内存存储临时数据,适配嵌入式、内存受限的设备;
  3. 无需稳定排序的场景:因堆排序不稳定,若要求相同值元素的相对顺序不变,需选择归并排序、冒泡排序等稳定算法。

定义代码示例

// 堆排序:调整堆结构(最大堆) bool XSort::Heapify(int* pArray, int nLength,int nIndex) { if (!pArray || nLength <= 0 || nIndex < 0 || nIndex >= nLength) { return false; } ​ int nEndIndex = nLength - 1;  // 需要判断存在 int nLeftIndex = 2 * nIndex + 1;  // 左孩子索引 for (; nLeftIndex <= nEndIndex; nIndex = nLeftIndex, nLeftIndex = 2 * nIndex + 1)  // 循环构成大根堆 { if (nLeftIndex < nEndIndex && pArray[nLeftIndex] < pArray[nLeftIndex + 1]) nLeftIndex += 1; if (pArray[nIndex] < pArray[nLeftIndex]) swap(pArray[nIndex], pArray[nLeftIndex]); else break; } return true; }

// 堆排序实现 bool XSort::HeapSort(int* pArray, int nLength) { if (!pArray|| nLength <= 1) { return false; } ​ int nEndIndex = nLength - 1; ​ for (int i = (nEndIndex - 1) / 2; i >= 0; --i) { Heapify(pArray, nLength, i); } ​ while (nEndIndex > 0) { swap(pArray[0], pArray[nEndIndex]); nEndIndex--; Heapify(pArray, nEndIndex + 1, 0); // 重新堆化根节点,堆的长度为nEndIndex+1 } return true; }

测试代码

//九大排序 #include "XSort.h" #include <iostream> // -------------------------- 测试核心函数 -------------------------- int main() {    // 1. 定义超大数组参数(可调整大小,建议根据电脑性能修改,10万/50万/100万)    const int BIG_ARRAY_SIZE = 100000;  // 超大数组长度(10万个元素,可按需增大) ​    // 2. 动态分配原始超大数组(避免栈溢出,静态数组无法存储超大数据)    int* pOriginalArray = new int[BIG_ARRAY_SIZE];    if (pOriginalArray == nullptr)   {        std::cout << "原始数组内存分配失败!" << std::endl;        return -1;   } ​    // 3. 初始化随机数种子(确保每次运行随机数不同)    srand((unsigned int)time(nullptr)); ​    // 4. 填充超大数组为随机整数(值域:0~9999,保证计数排序高效运行)    std::cout << "正在初始化 " << BIG_ARRAY_SIZE << " 个随机元素的超大数组..." << std::endl;    for (int i = 0; i < BIG_ARRAY_SIZE; ++i)   {        pOriginalArray[i] = rand() % 10000;  // 随机数范围:0~9999   }    std::cout << "数组初始化完成!" << std::endl << std::endl; ​    // 5. 创建XSort对象    XSort sortTool; ​    // 6. 定义临时数组(用于存储原始数组副本,避免排序后破坏原始数据)    int* pTempArray = new int[BIG_ARRAY_SIZE];    if (pTempArray == nullptr)   {        std::cout << "临时数组内存分配失败!" << std::endl;        delete[] pOriginalArray;        pOriginalArray = nullptr;        return -1;   } ​    // 7. 测试前5种排序算法并统计时间    clock_t start, end;    double elapsedTime;  // 耗时(单位:秒) ​    // -------------------------- 测试7:堆排序 --------------------------    std::memcpy(pTempArray, pOriginalArray, BIG_ARRAY_SIZE * sizeof(int));    std::cout << "\n开始测试【堆排序】..." << std::endl;    start = clock();    bool heapResult = sortTool.HeapSort(pTempArray, BIG_ARRAY_SIZE);    end = clock();    elapsedTime = (double)(end - start) / CLOCKS_PER_SEC;    if (heapResult)        std::cout << "堆排序成功!耗时:" << elapsedTime << " 秒" << std::endl;    else        std::cout << "堆排序失败!" << std::endl; ​    // 8. 释放动态内存,避免内存泄漏    delete[] pOriginalArray;    pOriginalArray = nullptr;    delete[] pTempArray;    pTempArray = nullptr; ​    std::cout << "\n所有排序测试完成!" << std::endl;    return 0; }

测试结果

Read more

【大数据存储与管理】分布式文件系统HDFS:03 HDFS的相关概念

【大数据存储与管理】分布式文件系统HDFS:03 HDFS的相关概念

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈大数据技术原理与应用 ⌋ ⌋ ⌋专栏系统介绍大数据的相关知识,分为大数据基础篇、大数据存储与管理篇、大数据处理与分析篇、大数据应用篇。内容包含大数据概述、大数据处理架构Hadoop、分布式文件系统HDFS、分布式数据库HBase、NoSQL数据库、云数据库、MapReduce、Hadoop再探讨、数据仓库Hive、Spark、流计算、Flink、图计算、数据可视化,以及大数据在互联网领域、生物医学领域的应用和大数据的其他应用。 【GitCode】专栏资源保存在我的GitCode仓库:https://gitcode.com/Morse_Chen/BigData_principle_application。 文章目录 * 一、块 * 二、名称节点和数据节点 * 三、第二名称节点 * 小结 本文介绍 HDFS 中的相关概念,包括块、名称节点和数据节点、第二名称节点。

By Ne0inhk
cJSON 1.7.19 源码深度分析:数据结构、解析流程与深度注释实践

cJSON 1.7.19 源码深度分析:数据结构、解析流程与深度注释实践

本文基于 cJSON 1.7.19 源码,从核心数据结构、JSON 解析/生成流程、内存管理到深度注释实践,系统梳理这一轻量级 JSON 库的设计与实现,适合 C 语言进阶与嵌入式开发学习。 目录 * 一、前言 * 二、核心数据结构:cJSON 结构体 * 2.1 结构体定义 * 2.2 内存布局(64 位系统示意) * 2.3 类型系统:位掩码设计 * 2.4 树状链表:一个例子 * 三、核心流程一:JSON 解析(字符串 → cJSON 树) * 3.1 调用链

By Ne0inhk

【超详细】Python FastAPI 入门:写给新手的“保姆级”教程

【超详细】Python FastAPI 入门:写给新手的“保姆级”教程(2025–2026 最新版) 这篇教程的目标是: 零基础 → 能独立写出生产级别的 RESTful API 预计认真跟着做完前 80%,你大概需要 3–10 天(每天 2–4 小时)。 目录(建议按顺序阅读) 1. 为什么选择 FastAPI(而不是 Flask / Django) 2. 环境准备(最稳的几种方式) 3. 第一个 FastAPI 程序(Hello World) 4. 核心概念速览(5 分钟建立大局观) 5. 路径参数、查询参数、请求体(

By Ne0inhk
python-flask职位数据采集与数据分析系统设计与实现Pycharm vue django

python-flask职位数据采集与数据分析系统设计与实现Pycharm vue django

目录 * 系统架构设计 * 后端实现(Flask/Django) * 前端实现(Vue.js) * 开发环境配置 * 联调与部署 * 关键实现技术 * 开发技术路线 * 源码lw获取/同行可拿货,招校园代理 :文章底部获取博主联系方式! 系统架构设计 采用前后端分离架构,后端使用Python Flask/Django处理数据采集与分析逻辑,前端使用Vue.js构建交互界面。Pycharm作为主要开发工具,整合前后端调试。 后端实现(Flask/Django) 数据采集模块 * 使用requests或scrapy爬取招聘网站数据,存储至MySQL/PostgreSQL数据库 * 设计数据模型:职位名称、公司、薪资、技能要求、地点等字段 * 实现定时爬虫任务:APScheduler定时触发爬取 数据分析模块 * 使用pandas进行数据清洗:处理缺失值、异常值 * 应用numpy和matplotlib进行薪资分布、技能词频等统计分析 * 构建薪资预测模型:线性回归或随机森林算法 # Flask示例路由@

By Ne0inhk