跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

堆排序原理与 C++ 实现

综述由AI生成堆排序是基于二叉堆数据结构的比较排序算法,平均和最坏时间复杂度均为 O(N log N)。文章阐述了二叉堆的定义、性质及数组存储方式,详细说明了堆的插入、删除和建堆操作原理。通过 C++ 代码演示了最小堆的实现过程,并解释了利用堆顶元素交换实现排序的逻辑。该方法适合处理大规模数据排序任务。

CodeArtist发布于 2025/2/5更新于 2026/6/1220 浏览
堆排序原理与 C++ 实现

堆排序

堆排序是一种时间复杂度为 O(N log N) 的常见排序方法。学习堆排序前,先讲解数据结构中的二叉堆。

二叉堆的定义

二叉堆是完全二叉树或者是近似完全二叉树。

二叉堆满足两个特性:

  1. 父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
  2. 每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。

当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。下图展示一个最小堆:

堆排序

由于其它几种堆(二项式堆、斐波纳契堆等)用得较少,一般将二叉堆就简称为堆。

堆的存储

一般都用数组来表示堆,i 结点的父结点下标就为 (i - 1) / 2。它的左右子结点下标分别为 2 * i + 1 和 2 * i + 2。如第 0 个结点左右子结点下标分别为 1 和 2。

堆排序

堆的操作——插入删除

下面先给出《数据结构 C++ 语言描述》中最小堆的建立插入删除的图解,再给出本人的实现代码,最好是先看明白图后再去看代码。

堆排序

堆的插入

每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列,现在的任务是将这个新数据插入到这个有序数据中——这就类似于直接插入排序中将一个数据并入到有序区间中,对照不难写出插入一个新数据时堆的调整代码:

// 新加入 i 结点 其父结点为 (i - 1) / 2
void MinHeapFixup(int a[], int i)
{
    int j, temp;

    temp = a[i];
    j = (i - 1) / 2;     // 父结点
    while (j >= 0 && i != 0)
    {
        if (a[j] <= temp)
            break;

        a[i] = a[j];     // 把较大的子结点往下移动,替换它的子结点
        i = j;
        j = (i - 1) / 2;
    }
    a[i] = temp;
}

更简短的表达为:

void MinHeapFixup(int a[], int i)
{
    for (int j = (i - 1) / 2; (j >= 0 && i != 0) && a[i] > a[j]; i = j, j = (i - 1) / 2)
        Swap(a[i], a[j]);
}

插入时:

// 在最小堆中加入新的数据 nNum
void MinHeapAddNumber(int a[], int n, int nNum)
{
    a[n] = nNum;
    MinHeapFixup(a, n);
}

堆的删除

按定义,堆中每次都只能删除第 0 个数据。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右儿子结点中找最小的,如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于从根结点将一个数据的'下沉'过程。下面给出代码:

// 从 i 节点开始调整,n 为节点总数 从 0 开始计算 i 节点的子节点为 2*i+1, 2*i+2
void MinHeapFixdown(int a[], int i, int n)
{
    int j, temp;

    temp = a[i];
    j = 2 * i + 1;
    while (j < n)
    {
        if (j + 1 < n && a[j + 1] < a[j]) // 在左右孩子中找最小的
            j++;

        if (a[j] >= temp)
            break;

        a[i] = a[j];     // 把较小的子结点往上移动,替换它的父结点
        i = j;
        j = 2 * i + 1;
    }
    a[i] = temp;
}
// 在最小堆中删除数
void MinHeapDeleteNumber(int a[], int n)
{
    Swap(a[0], a[n - 1]);
    MinHeapFixdown(a, 0, n - 1);
}

堆化数组

有了堆的插入和删除后,再考虑下如何对一个数据进行堆化操作。要一个一个的从数组中取出数据来建立堆吧,不用!先看一个数组,如下图:

堆排序

很明显,对叶子结点来说,可以认为它已经是一个合法的堆了即 20,60,65,4,49 都分别是一个合法的堆。只要从 A[4]=50 开始向下调整就可以了。然后再取 A[3]=30,A[2] = 17,A[1] = 12,A[0] = 9 分别作一次向下调整操作就可以了。下图展示了这些步骤:

堆排序

写出堆化数组的代码:

// 建立最小堆
void MakeMinHeap(int a[], int n)
{
    for (int i = n / 2 - 1; i >= 0; i--)
        MinHeapFixdown(a, i, n);
}

至此,堆的操作就全部完成了 (注 1),再来看下如何用堆这种数据结构来进行排序。

堆排序

首先可以看到堆建好之后堆中第 0 个数据是堆中最小的数据。取出这个数据再执行下堆的删除操作。这样堆中第 0 个数据又是堆中最小的数据,重复上述步骤直至堆中只有一个数据时就直接取出这个数据。

由于堆也是用数组模拟的,故堆化数组后,第一次将 A[0] 与 A[n - 1] 交换,再对 A[0…n-2] 重新恢复堆。第二次将 A[0] 与 A[n – 2] 交换,再对 A[0…n - 3] 重新恢复堆,重复这样的操作直到 A[0] 与 A[1] 交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。

void MinheapsortTodescendarray(int a[], int n)
{
    for (int i = n - 1; i >= 1; i--)
    {
        Swap(a[i], a[0]);
        MinHeapFixdown(a, 0, i);
    }
}

注意使用最小堆排序后是递减数组,要得到递增数组,可以使用最大堆。

由于每次重新恢复堆的时间复杂度为 O(log N),共 N - 1 次重新恢复堆操作,再加上前面建立堆时 N / 2 次向下调整,每次调整时间复杂度也为 O(log N)。二次操作时间相加还是 O(N log N)。故堆排序的时间复杂度为 O(N log N)。STL 也实现了堆的相关函数,可查阅相关文档。

注 1 作为一个数据结构,最好用类将其数据和方法封装起来,这样既便于操作,也便于理解。此外,除了堆排序要使用堆,另外还有很多场合可以使用堆来方便和高效的处理数据,以后会一一介绍。

目录

  1. 堆排序
  2. 二叉堆的定义
  3. 堆的存储
  4. 堆的操作——插入删除
  5. 堆的插入
  6. 堆的删除
  7. 堆化数组
  8. 堆排序
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Flowise 结合 Web Scraping 的数据采集流程
  • Vue Element UI 日历组件实现日程安排与区间查询
  • 工业视觉缺陷检测算法总结:从传统到深度学习,5 类核心算法
  • llama.cpp 技术指南:从底层原理到实战部署
  • 华为盘古大模型 3.0 发布:架构解析与行业应用分析
  • FastAPI+Python 前后端交互实战:用户登录注册与信息管理
  • Windows + WSL + Ubuntu 安装 OpenClaw 及飞书百炼集成指南
  • Formality 原语概念详解
  • Python 爬虫结合比迪丽 AI 绘画模型自动化采集艺术素材
  • Spring AOP 实现基于业务逻辑的动态数据源切换
  • Midjourney 第三方 API 服务技术原理与合规边界探讨
  • Python 生成 4 位随机数的多种实现方法与最佳实践
  • Java 滑动窗口算法实战:LeetCode 经典题解
  • C++ 继承入门:从基础概念定义到默认成员函数
  • 开源智能排产系统 JVS-APS:算法驱动与低代码融合
  • FPGA 开发从入门到精通
  • AIGC 产品经理的定义、职责及与 AI 产品经理的区别
  • Llama3 中文通用 Agent 微调模型实战教程
  • 医疗大模型商业化进程:挑战、方案与落地路径
  • 数据结构:双向链表详解(结构、实现与算法实战)

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online