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

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

.

个人主页:晓风飞
专栏:数据结构|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语言示例展示了堆排序的实现,希望能帮助你更好地理解这个强大的算法。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一天吃透Java并发面试八股文

内容摘自我的学习网站&#xff1a;topjavaer.cn 分享50道Java并发高频面试题。 线程池 线程池&#xff1a;一个管理线程的池子。 为什么平时都是使用线程池创建线程&#xff0c;直接new一个线程不好吗&#xff1f; 嗯&#xff0c;手动创建线程有两个缺点 不受控风险频繁创…

通过离散点拟合曲线

文章目录 使用离散点拟合曲线参考代码路径:作者源码:测试代码效果图:k3k4k5 使用离散点拟合曲线 参考代码路径: https://www.bragitoff.com/2015/09/c-program-for-polynomial-fit-least-squares/ https://gist.github.com/Thileban/272a67f2affdc14a5f69ad3220e5c24b https:/…

PID横向控制和仿真实现

文章目录 1. PID介绍2. PID横向控制原理3. 算法和仿真实现 1. PID介绍 PID是一种常见的控制算法&#xff0c;全称为Proportional-Integral-Derivative&#xff0c;即比例-积分-微分控制器。PID控制器是一种线性控制器&#xff0c;它将设定值与实际值进行比较&#xff0c;根据误…

基于51单片机的模拟量输入输出通道实验

实验一 模拟量输入输出通道实验&#xff08;C51&#xff09; 一、实验目的&#xff1a; 1、了解A/D、D/A转换的基本原理。 2、了解A/D转换芯片ADC0809、D/A转换芯片DAC0832的性能及编程方法。 3、掌握过程通道中A/D转换与D/A转换与计算机的接口方法。 4、了解计算机如何进…

VSCode 正则表达式 匹配多行

VS Code 正则表达式匹配多行 (.|\n)*? //test.js const test {str: VS Code 正则表达式匹配多行VS Code 正则表达式匹配多行VS Code 正则表达式匹配多行VS Code 正则表达式匹配多行VS Code 正则表达式匹配多行VS Code 正则表达式匹配多行VS Code 正则表达式匹配多行VS Code …

数据库作业二

一&#xff0c;单表查询 1.创建表 1、显示所有职工的基本信息。 2、查询所有职工所属部门的部门号&#xff0c;不显示重复的部门号。 3、求出所有职工的人数。 4、列出最高工和最低工资。 5、列出职工的平均工资和总工资。 6、创建一个只有职工号、姓名和参加工作的…

【WPF.NET开发】WPF中的版式

本文内容 改进的文本质量和性能丰富的版式增强的国际文本支持增强的字体支持新的文本应用程序编程接口 (API) 本主题介绍 WPF 的主要版式功能。 这些功能包括改进的文本呈现质量和性能、OpenType 版式支持、增强的国际文本、增强的字体支持和新的文本应用程序编程接口 (API)。…

2024多系统萎缩最新全球特效药治疗进展

多系统萎缩是一种罕见的神经退行性疾病&#xff0c;由于缺乏有效的治疗方法&#xff0c;患者经常面临症状无法缓解和生活品质下降的困扰。然而&#xff0c;近期刘家峰大夫基于中医理论研究和临床实践&#xff0c;采用中药治疗多系统萎缩取得了显著疗效&#xff0c;给患者带来了…

VUE好看的个人简历模板

文章目录 1.设计来源1.1 首页界面1.2 关于我界面1.3 我的资历界面1.4 项目经验界面1.5 我的技能界面1.6 联系我界面 2.效果和源码2.1 动态效果2.2 源码目录结构 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/…

RMI简介

RMI 介绍 RMI (Remote Method Invocation) 模型是一种分布式对象应用&#xff0c;使用 RMI 技术可以使一个 JVM 中的对象&#xff0c;调用另一个 JVM 中的对象方法并获取调用结果。这里的另一个 JVM 可以在同一台计算机也可以是远程计算机。因此&#xff0c;RMI 意味着需要一个…

线程安全2

文章目录 锁的可重入性死锁内存可见性引起的线程安全 锁的可重入性 直观来看这个代码不能运行 为啥没有出现阻塞&#xff1f; 当前由于是同一个线程&#xff0c;此时的锁对象&#xff0c;就知道了第二次加锁的线程&#xff0c;就是持有锁的线程&#xff0c;第二次操作&#xff…

Linux下如何快速调试I2C设备

Linux下如何快速调试I2C设备 目录 1 什么场景下需要快速调试I2C设备 2 如何快速调试I2C设备 3 如何获取I2C Tools工具集 3.1 获取I2C Tools工具集源码 3.2 编译I2C Tools工具集源码 3.3 为设备添加I2C Tools工具集 4 如何使用I2C Tools工具集 5 小结 1 什么场景下需要快…

VScode设置自动添加自定义注释及修改字体

首先安装snippet mac可以键入commanp&#xff0c;输出> 选择自己所需的需要自动添加的文件类型配置文件 安装自己的需要修改 "Print to console": {"prefix": "xx", // 自己键入内容"body": [ // 注释信息"// xxx …

SpringMVC RESTful案例

文章目录 1、准备工作2、功能清单3、具体功能&#xff1a;访问首页a>配置view-controllerb>创建页面 4、具体功能&#xff1a;查询所有员工数据a>控制器方法b>创建employee_list.html 5、具体功能&#xff1a;删除a>创建处理delete请求方式的表单b>删除超链接…

docker部署私人云盘nextcloud

首先查看效果 1.拉取镜像 docker pull nextcloud 2.创建目录 mkdir -p /data/nextcloud/{config,data,apps} 3.创建实例 docker run -itd --name yznextcloud -v /data/nextcloud/config:/var/www/html/config -v /data/nextcloud/data:/var/www/html/data -v /data/nextc…

Minikube安装

文章目录 简介安装仪表盘 简介 Minikube是一个轻量级的工具&#xff0c;用于在本地机器上运行K8s集群。它允许开发人员在没有云环境的情况下进行K8s应用程序的开发和测试。 和k8s需要一个主机两个从机不同&#xff0c;Minikube用kubectl来控制节点&#xff0c;相当于在虚拟机…

如何制作网址链接活码?网址二维码生成器的使用方法

将网址转二维码图片来使用&#xff0c;是现在很常用的一种二维码类型&#xff0c;一般网址可以根据自己的用途来制作静态码或者活码两种形式。其中静态码只是单纯将网址链接转换成二维码&#xff0c;无法统计与修改&#xff0c;而生成网址活码可以在二维码图片不变情况下替换其…

基于RNN的模型

文本数据是一种典型的具有序列结构的数据&#xff0c;因为文本通常是由一系列的词语或字符组成的序列。每个词语或字符在文本中都有特定的位置和顺序&#xff0c;这种有序的结构对于理解和处理文本的含义至关重要。因此&#xff0c;多数情况下需要使用时间序列建模来完成相应的…

按键精灵调用奥迦插件实现图色字识别模拟键鼠操作源码

奥迦插件于2019年9月开始开发,在Windows 10操作系统上使用Visual Studio 2019编写,适用于所有较新的Windows平台,是一款集网络验证,深度学习,内核,视觉,文字,图色,后台,键鼠,窗口,内存,汇编,进程,文件,网络,系统,算法及其它功能于一身的综合插件 插件使用C语言和COM技术编写,是…