堆的应用:Top-K问题

简介: 堆的应用中关于TOP-K的问题的详细解题思路(C语言实现)

 朋友们、伙计们,我们又见面了,本期来给大家解读一下堆的应用--Top-K问题的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

数据结构与算法专栏数据结构与算法

个  人  主  页stackY、

C 语 言 专 栏C语言:从入门到精通

image.gif编辑

目录

前言:

1.何为Top-K

2.解题思路

3.代码演示

3.1创建数据

3.2建小堆、排序

4.完整代码


紫蓝色几何渐变科技互联网微信公众号封面 (1).gif

前言:

前面我们学了堆的实现、 堆排序算法,堆排序算法中我们知道了使用堆排序要向下调整建堆排升序我们建的是大堆、排降序我们建小堆,其中的细节就不做赘述,堆排序算法让我们初步了解了堆这种特殊的数据结构的作用,那么在本篇再来给大家带来一个关于堆的应用的问题:Top-K问题

1.何为Top-K

TopK问题指的是在一个包含N个元素的序列中,找出其中前K个最大或者最小的元素。通常情况下,K远小于N,因此这个问题也被称为“求前K大(小)元素”问题。TopK问题在数据分析、排序算法、搜索引擎等领域都有着广泛的应用。

举个栗子:

在100000个随机数数中找出前10个最大的数。

2.解题思路

TopK问题在这里有多种解决的办法,我们取最优解:

1. 对N个数据进行从小到大或者从大到小排序,然后取出前K个数据便是我们想要找的数据。

2. 联想我们学过的堆排序,将这N个数建立大堆或者小堆,依次取出前K个数据即可。

但是这两种方法都存在一个问题:首先排序本身就是比较费劲的事情,如果N非常大,排序需要的代价很大,并且将N个数建堆也不合理,因为数据过多计算机内存容纳不下那么多的数据,就会存储到磁盘文件中,我们知道数据在磁盘文件中处理的速度是比较慢的,因此我们需要想一个更方便的方法(假设要在N个数据中找前K个最大的数据)。

改进方法:

1. 先建立K个数的小堆

2. 再用后面N-K个数依次插入,如果比堆顶的数据大,就替换进堆

3. 最后这个小堆中的数据就是要找的前K个数。

3.代码演示

3.1创建数据

我们为了来演示代码的正确性,我们先来创建数据,将10W个数据先保存在一个文件中,然后找出这10W个数据中最大的前五个数据。这里就需要用到文件操作的相关知识(进阶C语言:文件操作)和一个随机数的种子(猜数字小游戏)。

代码演示:

//创建数据
void CreateNDate()
{
  int n = 100000;           //总数据个数
  srand((unsigned int)time(NULL));  //设置随机数生成器
  const char* file = "data.txt";
  //打开文件
  FILE* fin = fopen(file, "w");
  if (fin == NULL)
  {
    perror("fopen fial");
    return;
  }
  for (int i = 0; i < n; i++)
  {
    //数据范围在10W之内
    int x = rand() % 100000;
    //写文件
    fprintf(fin, "%d\n", x);
  }
  //关闭文件
  fclose(fin);
  fin = NULL;
}
image.gif

3.2建小堆、排序

先建K个数的小堆,然后使用剩下的N-K个数据依次插入比较,比堆顶大的数据就替换进堆,然后向下调整,直到将文件中的所有数据比较完成,这时这个K个数的堆就是我们要找的数据(这里建小堆为什么不建大堆?因为小堆的堆顶的元素是这个堆中最小的元素,如果有比它大的就可以筛选进入堆,如果建成大堆,那么堆顶的数就是最大的,那么遇到次大的就进不来,所以需要建小堆)。

代码演示:

void PrintTopK(int k)
{
  //打开文件
  const char* file = "data.txt";
  FILE* fout = fopen(file, "r");
  if (fout == NULL)
  {
    perror("fopen fial");
    return;
  }
  //先建立K个数的小堆
  int* kminheap = (int*)malloc(sizeof(int) * k);
  if (kminheap == NULL)
  {
    perror("malloc fail");
    return;
  }
  //将文件中的前K个数据先储存在块空间中
  for (int i = 0; i < k; i++)
  {
    fscanf(fout, "%d", &kminheap[i]);
  }
  //向下调整将这K个数建成小堆
  for (int i = (k - 1 - 1) / 2; i >= 0; i--)
  {
    AdjustDown(kminheap, k, i);
  }
  int num = 0;
  //文件读取结束表示排序完成
  while (!feof(fout))
  {
    //依次比较
    fscanf(fout, "%d", &num);
    //符合则进行替换,向下调整
    if (num > kminheap[0])
    {
      kminheap[0] = num;
      AdjustDown(kminheap, k, 0);
    }
  }
  for (int i = 0; i < k; i++)
  {
    printf("%d ", kminheap[i]);
  }
  printf("\n");
  //关闭文件
  fclose(fout);
  fout = NULL;
}
image.gif

4.完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//Top - K问题
//交换函数
void Swap(int* p1, int* p2)
{
  int tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}
//向下调整
void AdjustDown(int* a, int n, int parent)
{
  //假设左孩子为左右孩子中最小的节点
  int child = parent * 2 + 1;
  while (child < n)  //当交换到最后一个孩子节点就停止
  {
    if (child + 1 < n  //判断是否存在右孩子
      && a[child + 1] < a[child]) //判断假设的左孩子是否为最小的孩子
    {
      child++;   //若不符合就转化为右孩子
    }
    //判断孩子和父亲的大小关系
    if (a[child] < a[parent])
    {
      Swap(&a[child], &a[parent]);
      //更新父亲和孩子节点
      parent = child;
      child = parent * 2 + 1;
    }
    else
    {
      break;
    }
  }
}
//创建数据
void CreateNDate()
{
  int n = 100000;           //总数据个数
  srand((unsigned int)time(NULL));  //设置随机数生成器
  const char* file = "data.txt";
  //打开文件
  FILE* fin = fopen(file, "w");
  if (fin == NULL)
  {
    perror("fopen fial");
    return;
  }
  for (int i = 0; i < n; i++)
  {
    //数据范围在10W之内
    int x = rand() % 100000;
    //写文件
    fprintf(fin, "%d\n", x);
  }
  //关闭文件
  fclose(fin);
  fin = NULL;
}
void PrintTopK(int k)
{
  //打开文件
  const char* file = "data.txt";
  FILE* fout = fopen(file, "r");
  if (fout == NULL)
  {
    perror("fopen fial");
    return;
  }
  //先建立K个数的小堆
  int* kminheap = (int*)malloc(sizeof(int) * k);
  if (kminheap == NULL)
  {
    perror("malloc fail");
    return;
  }
  //将文件中的前K个数据先储存在块空间中
  for (int i = 0; i < k; i++)
  {
    fscanf(fout, "%d", &kminheap[i]);
  }
  //向下调整将这K个数建成小堆
  for (int i = (k - 1 - 1) / 2; i >= 0; i--)
  {
    AdjustDown(kminheap, k, i);
  }
  int num = 0;
  //文件读取结束表示排序完成
  while (!feof(fout))
  {
    //依次比较
    fscanf(fout, "%d", &num);
    //符合则进行替换,向下调整
    if (num > kminheap[0])
    {
      kminheap[0] = num;
      AdjustDown(kminheap, k, 0);
    }
  }
  for (int i = 0; i < k; i++)
  {
    printf("%d ", kminheap[i]);
  }
  printf("\n");
  //关闭文件
  fclose(fout);
  fout = NULL;
}
int main()
{
  //创建数据
  CreateNDate();
  //求前5个最大数
  PrintTopK(5);
  return 0;
}
image.gif

image.gif编辑

可以看到在10W个数据中找到了前5个最大的数。

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!

目录
相关文章
|
存储 关系型数据库 MySQL
mysql数据库备份与恢复
MySQL数据库的备份与恢复是确保数据安全性和业务连续性的关键操作。
552 4
|
弹性计算
2024年阿里云免费云服务器及学生云服务器申请教程参考
2024年阿里云继续推出免费学生云服务器与免费试用云服务器,其中学生云服务器最长可免费7个月(1个月首次领用+6个月免费续领),免费试用云服务器分为个人免费云服务器和企业免费云服务器,最长免费试用时长是3个月。下面小编来介绍一下阿里云免费云服务器及学生云服务器的申请教程。
53960 54
2024年阿里云免费云服务器及学生云服务器申请教程参考
|
缓存 编译器
BOLT 二进制反馈优化技术
大型应用的代码往往达到数十甚至上百MB,这导致在程序执行时缓存机制无法充分利用,导致大量时间花费在CPU和内存链路上。通过对热点函数的布局进行优化,我们可以更好地利用CPU cache,从而获得较为可观的性能提升。针对这一问题,在编译技术上有PGO和Bolt两种解决办法,两者都是一种通过收集程序在运行时如跳转,调用关系,函数热度等执行信息,这些收集到的程序运行情况数据(profile data),可以更好地指导一些程序优化的策略,如是否对函数进行内联,以及对基本块和函数布局的排布来提高特定场景下的程序性能。
2487 2
BOLT 二进制反馈优化技术
|
存储 自然语言处理 前端开发
领域驱动设计(DDD)-基础思想
一、序言     领域驱动设计是一种解决业务复杂性的设计思想,不是一种标准规则的解决方法。在领域驱动设计理念上,各路大侠的观点也是各有不同,能力有限、欢迎留言讨论。 二、领域驱动设计 DDD是什么 wiki释义:     领域驱动设计(英语:Domain-driven design,缩写 DDD)是一种通过将实现连接到持续进化的模型[1]来满足复杂
7830 0
|
JavaScript
NATAPP使用教程(内网穿透)
NATAPP使用教程(内网穿透)
2016 0
|
9月前
|
数据挖掘 Python
Pandas时间序列处理:日期与时间
本文介绍Pandas在处理时间序列数据时的基础概念、常见问题及解决方案。涵盖时间戳、时间间隔和周期等概念,详细讲解日期格式转换、缺失值处理、时间间隔计算和重采样等操作,并通过代码示例说明如何解决`ParserError`和`OutOfBoundsDatetime`等常见报错。掌握这些知识有助于高效处理时间序列数据,提高数据分析的质量和效率。
591 75
|
7月前
|
存储 自然语言处理 数据库
【数据结构进阶】AVL树深度剖析 + 实现(附源码)
在深入探讨了AVL树的原理和实现后,我们不难发现,这种数据结构不仅优雅地解决了传统二叉搜索树可能面临的性能退化问题,还通过其独特的平衡机制,确保了在任何情况下都能提供稳定且高效的查找、插入和删除操作。
560 19
|
11月前
|
开发框架 前端开发 JavaScript
常见的跨平台开发框架
【10月更文挑战第25天】这些跨平台开发框架各有特点,开发者可以根据项目的具体需求、团队的技术栈和对性能、用户体验的要求等因素来选择合适的框架进行开发。
|
机器学习/深度学习 分布式计算 数据可视化
使用Python进行大规模数据处理和分析
总而言之,Python作为一种强大而灵活的编程语言,在大规模数据处理和分析领域有着广泛的应用。通过不断学习和探索,我们可以充分发挥Python的潜力,为解决现实世界的数据挑战做出更大的贡献。让我们继续深入学习、探索和创造,在数据科学的道路上不断前行!