堆排序——高效解决TOP-K问题

news2024/11/19 7:49:20

在这里插入图片描述在这里插入图片描述

.

个人主页:晓风飞
专栏:数据结构|Linux|C语言
路漫漫其修远兮,吾将上下而求索


文章目录

  • 引言
  • 什么是堆?
  • 建堆
  • 堆排序:
    • 排序的最终结果
  • 堆排序实现
    • 函数声明
    • 交换函数 `Swap`
    • 下沉调整 `DnAdd`
    • 堆排序函数 `HeapSort`
    • 主函数
  • 文件中找TopK问题
    • 什么是TOP-K问题
    • 堆排序的解决方案
    • 操作应用
    • 结论


引言

在数据结构和算法的世界中,排序是一个基本而重要的概念。堆排序是一种高效的排序算法,它利用堆这一数据结构的特性来实现。在这篇文章中,我们将深入探索堆排序的原理,并通过C语言示例来展示它的实现。


什么是堆?

堆是一种特殊的完全二叉树,其中每个父节点的值都大于或等于其子节点的值(最大堆),或者每个父节点的值都小于或等于其子节点的值(最小堆)。在堆排序中,我们通常使用最大堆。


建堆

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


堆排序:

将堆顶元素(最大值)与最后一个元素交换,然后减少堆的大小,并重新对堆顶元素执行下沉操作。重复此过程,直到堆的大小为1。建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
以下是使用实现堆排序的基本步骤:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

排序的最终结果

在这里插入图片描述


堆排序实现

函数声明

交换函数 :
void Swap(HPDataType* p1, HPDataType* p2)
下沉调整 :
void DnAdd(HPDataType* a, HPDataType parent, int size)
堆排序函数:
void HeapSort(int* a, int n)

交换函数 Swap

void Swap(HPDataType* p1, HPDataType* p2) {
  HPDataType tmp = 0;  // 临时变量用于交换
  tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}

Swap 函数的作用是交换两个元素的值。这在堆排序中非常重要,特别是在删除堆顶元素或重构堆的过程中。此函数通过传递指向数据的指针来直接修改原数组。

下沉调整 DnAdd

void DnAdd(HPDataType* a, HPDataType parent, int size) {
  int child = parent * 2 + 1; // 找到左子节点
  while (child < size) {
    // 检查右子节点是否存在,以及比较左右两个子节点的值
    if (child + 1 < size && a[child + 1] > a[child]) {
      child++; // 选择较大的子节点
    }
    // 如果子节点大于父节点,则需要交换
    if (a[child] > a[parent]) {
      Swap(&a[child], &a[parent]); // 交换父子节点
      parent = child; // 更新父节点位置
      child = parent * 2 + 1;
    } else {
      break; // 如果不需要交换,则终止循环
    }
  }
}

DnAdd 函数实现了堆的下沉调整,是构建和维护堆的关键操作。如果子节点的值大于父节点的值,则需要进行交换,以确保维护最大堆的性质。

堆排序函数 HeapSort

void HeapSort(int* a, int n) {
  // 构建初始大顶堆
  for (int i = (n / 2) - 1; i >= 0; i--) {
    DnAdd(a, i, n);
  }
  
  // 从堆中逐个移除元素并进行排序
  for (int end = n - 1; end > 0; end--) {
    Swap(&a[0], &a[end]); // 将最大的元素(堆顶)移动到数组的末尾
    DnAdd(a, 0, end); // 对剩余的堆进行向下调整
  }
}

HeapSort 函数首先通过调用 DnAdd 函数建立一个大顶堆。之后,通过不断移除堆顶元素(数组中的最大元素)并将其移动到数组的末尾,然后再次调用 DnAdd 函数进行下沉调整,最终达到整个数组的排序目的。

主函数

Copy code
int main() {
  int arr[] = { 8, 6, 4, 2, 0, 9, 4 };
  HeapSort(arr, sizeof(arr) / sizeof(arr[0]));
  for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
    printf("%d ", arr[i]);
  }
}

在主函数中,我们定义了一个未排序的数组 arr 并调用了 HeapSort 函数对其进行排序。排序完成后,使用一个循环来打印排序后的数组元素。通过main函数中的测试数组,我们可以看到HeapSort函数如何将无序数组转换成一个有序序列。我们也可以通过更换数组中的元素来测试不同的数据集。


文件中找TopK问题

什么是TOP-K问题

TOP-K问题是指在一个大数据集中找到前K个最大或最小的元素。这个问题在多个领域都非常常见,比如排名、选举、统计和游戏等。常见的例子包括找到考试成绩中的前10名、世界500强企业或者游戏中最活跃的100名玩家。

当数据量非常大时,简单的排序方法可能会因为数据量超过内存限制而变得不可行。此外,完整的排序操作的时间复杂度为
O(nlogn),这在数据量极大时效率低下。

堆排序的解决方案

堆排序提供了一个更为高效的解决方案,时间复杂度为O(nlogK),这对于大数据集来说是一个巨大的提升。解决TOP-K问题的基本思路是:

用数据集合中前K个元素来建堆:
如果我们需要找到前K个最大的元素,则建立一个最小堆。
如果我们需要找到前K个最小的元素,则建立一个最大堆。
用剩余的N-K个元素依次与堆顶元素比较:
如果当前元素比堆顶元素大(在寻找最大元素时)或小(在寻找最小元素时),则将其与堆顶元素替换,并重新调整堆。
提取堆中的元素:

经过上述过程后,堆中剩余的K个元素就是我们要找的前K个最大或最小的元素。

操作应用

在文件中建立100000个数,查找前5个数最大的数

void PrintTopK(const char* file, int k)
{
  FILE* fout = fopen(file, "r");
  if (fout == NULL)
  {
    perror("fopen error");
    return;
  }


  // 建一个k个数的最小堆
  int* minheap = (int*)malloc(sizeof(int) * k);
  if (minheap == NULL)
  {
    perror("malloc error");
    fclose(fout); // 记得关闭文件指针
    return;
  }


  // 读取前k个数,以构建最小堆
  for (int i = 0; i < k; i++)
  {
    if (fscanf(fout, "%d", &minheap[i]) != 1) // 检查fscanf的返回值
    {
      perror("fscanf error");
      free(minheap);
      fclose(fout);
      return;
    }
    UpAdd(minheap, i); // 由于是读取前k个数,这里应该是UpAdd
  }


  // 遍历文件中剩余的数,维护最小堆
  int x = 0;
  while (fscanf(fout, "%d", &x) != EOF)
  {
    if (x > minheap[0]) // 只有新的数比堆顶大时,才替换并进行下沉
    {
      minheap[0] = x;
      DnAdd(minheap, 0, k); // 注意这里是对堆顶进行下沉,所以传入的应该是0
    }
  }


  // 输出结果
  HeapSort(minheap, k); // 排序最小堆,使之按照顺序输出
  for (int i = 0; i < k; i++)
  {
    printf("%d ", minheap[i]);
  }
  printf("\n");


  free(minheap); // 释放内存
  fclose(fout); // 关闭文件
}

结论

堆排序是一种非常有效的排序算法,特别适用于大数据集。通过利用堆的属性,它能够以 (O(n \log n)) 的时间复杂度进行排序。这篇文章通过C语言示例展示了堆排序的实现,希望能帮助你更好地理解这个强大的算法。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day-09 删除排序链表中的重复元素

思路 从前往后遍历链表&#xff0c;当当前节点的值与下一个节点值相等时&#xff0c;删除下一节点&#xff1b;否则向后移动一个节点&#xff0c;继续遍历 解题方法 while(p!null&&p.next!null){ if(p.next.valp.val)p.nextp.next.next;//当前节点的值与下一个节点值相…

WorkPlus卓越的即时通讯工具,助力企业提升工作效率

在当今快节奏的商业环境中&#xff0c;高效沟通和协作是企业成功的关键。而即时通讯作为实现高效沟通的利器&#xff0c;成为了现代企业不可或缺的一部分。作为一款领先的即时通讯工具&#xff0c;WorkPlus以其卓越的性能和独特的功能&#xff0c;助力企业打造高效沟通和协作的…

Docker实战07|Docker增加容器资源限制

上一篇文章中&#xff0c;讲解了Docker run的具体流程以及Docker是如何改变PID为1的底层原理。 具体文章可见《Docker就应该这么学-06》 有需要的小伙伴可以回顾一下。 接下来本文会详细介绍一下Docker 是如何增加容器的资源限制 增加容器的资源限制 获取代码 git clone …

Python之循环判断语句

一、if判断语句 1. if...else if 条件: 满足条件时要做的事情1 满足条件时要做的事情2 ...... else: 不满足条件时要做的事情1 不满足条件时要做的事情2 ...... # -*- coding:utf-8 -*- age input("请输入年龄:") age int(age) if age > 18:print("已经成…

C# 导出EXCEL 和 导入

使用winfrom简单做个界面 选择导出路径 XLSX起名字 打开导出是XLSX文件 // 创建Excel应用程序对象Excel.Application excelApp new Excel.Application();excelApp.Visible false;// 创建工作簿Excel.Workbook workbook excelApp.Workbooks.Add(Type.Missing);Excel.Works…

【PlantUML】- 时序图

写在前面 本篇文章&#xff0c;我们来介绍一下PlantUML的时序图。这个相对类图来讲&#xff0c;比较简单&#xff0c;也不需要布局。读完文章&#xff0c;相信你就能实际操作了。 目录 写在前面一、基本概念二、具体步骤1.环境说明2.元素3.语法4.示例 三、参考资料写在后面系列…

DNS分离解析

一、介绍 分离解析的域名服务器实际也是主域名服务器&#xff0c;这里主要是指根据不同的客户端提供不同的域名解析记录。比如来自内网和外网的不同网段地址区域的客户机请求解析同一域名时&#xff0c;为其提供不同的解析结果&#xff0c;得到不同的IP地址。 DNS的分离…

基于Java SSM框架实现企业车辆管理系统项目【项目源码】

基于java的SSM框架实现企业车辆管理系统演示 JSP技术 JSP技术本身是一种脚本语言&#xff0c;但它的功能是十分强大的&#xff0c;因为它可以使用所有的JAVA类。当它与JavaBeans 类进行结合时&#xff0c;它可以使显示逻辑和内容分开&#xff0c;这就极大的方便了运动员的需求…

Transformer如何工作

Transformer如何工作 Transformer工作可以分为两个阶段&#xff1a;Inference&#xff08;推理&#xff09;和Training&#xff08;训练&#xff09; ​ ​​​ ‍ ‍

尼科彻斯定理----C语言

大家好我是Beilef许久未见了&#xff0c;小弟学校考试刚结束。这个过程懂的都懂。痛------ 文章目录 目录 文章目录 前言(一不好懂可以直接跳到二&#xff09; 一、尼科彻斯定理是什么&#xff1f; 二、尼科彻斯定理解析 这是ai的回答 尼科彻斯定理&#xff08;Nikomačs theor…

JS 作用域和预解析

作用域 通常来说&#xff0c;一段程序代码中所用到的名字并不总是有效和可用的&#xff0c;而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用提高了程序逻辑的局部性&#xff0c;增强了程序的可靠性&#xff0c;减少了名字冲突。 作用域分为全局作用域和局…

PHP反序列化总结4--原生类总结

原生类的简要介绍以及原生类和反序列化的关系 PHP 原生类指的是 PHP 内置的类&#xff0c;它们可以直接在 PHP 代码中使用且无需安装或导入任何库&#xff0c;相当于代码中的内置方法例如echo &#xff0c;print等等可以直接调用&#xff0c;但是原生类就是可以就直接php中直接…

主键(设置/删除方法,复合主键),唯一键,主键和唯一键的应用差异,自增长字段(如何实现,查看上次的值),外键(引入,外键约束)

目录 主键(primary key) 介绍 作用 设置主键 建表前 建表后 复合主键 介绍 示例 删除主键 唯一键(unique) 介绍 意义 示例 建表 主键 唯一键 插入数据示例 主键和唯一键的应用差异 主键 唯一键 索引 auto_increment 介绍 示例 自动插入 手动插入 …

达梦数据实时同步软件DMHS介绍和原理

1、产品介绍 达梦数据实时同步软件&#xff08;以下简称 DMHS&#xff09;是支持异构环境的高性能、高可靠、高可扩展数据库实时同步复制系统。该产品采用基于日志的结构化数据复制技术&#xff0c;不依赖主机上源数据库的触发器或者规则&#xff0c;对主机源数据库系统几乎无影…

系列六、Spring Security中的认证 授权 角色继承

一、Spring Security中的认证 & 授权 & 角色继承 1.1、概述 关于Spring Security中的授权&#xff0c;请参考【系列一、认证 & 授权】&#xff0c;这里不再赘述。 1.2、资源类 /*** Author : 一叶浮萍归大海* Date: 2024/1/11 20:58* Description: 测试资源*/ Re…

案例120:基于微信小程序的校友林设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

蓝桥杯省赛无忧 STL 课件18 总结

3226 宝藏排序 II 1624 小蓝吃糖果 2490 小蓝的括号串1 1531快递分拣

ubuntu 2022.04 安装vcs2018和verdi2018

主要参考网站朋友们的作业。 安装时参考&#xff1a; ubuntu18.04安装vcs、verdi2018_ubuntu安装vcs-CSDN博客https://blog.csdn.net/qq_24287711/article/details/130017583 编译时参考&#xff1a; 【ASIC】VCS报Error-[VCS_COM_UNE] Cannot find VCS compiler解决方法_e…

远程登陆利器 ssh

文章目录 远程登陆利器 ssh登陆远程服务器指定用户名多数情况的登陆方式查看服务器的时间指定端口更多信息 远程登陆利器 ssh ssh命令是openssh套件中的客户端连接工具&#xff0c;使用加密协议实现安全的远程登录服务器&#xff0c;实现对服务器的远程管理。 官方定义为&…

响应式编程初探-自定义实现Reactive Streams规范

最近在学响应式编程&#xff0c;这里先记录下&#xff0c;响应式编程的一些基础内容 1.名词解释 Reactive Streams、Reactor、WebFlux以及响应式编程之间存在密切的关系&#xff0c;它们共同构成了在Java生态系统中处理异步和响应式编程的一系列工具和框架。 Reactive Streams…