【堆的应用】TOP-K问题

简介: 对于TOP-K问题,可能第一反应是用排序,将前几名排序出来,但当数据量很大很大时,排序就不好处理了。可能数据一下不能完全加载到内存中去。

TOP-K问题:即求数据结合中前K个最大数或者最小数,一般情况下数据量比较大。常用的方法是建堆处理


c69669d21df244c787cce5637d52a15b.png93b6b88196fc441280b30d36273d8606.gif


①.生活案例


在生活中有很多涉及top-k问题,比如世界五百强,学校专业前几名,富豪榜等等。


对于TOP-K问题,可能第一反应是用排序,将前几名排序出来,但当数据量很大很大时,排序就不好处理了。可能数据一下不能完全加载到内存中去。


而解决这样的问题,最好的方式是用堆来处理。


②.解决思路:


1.取数据集合中前K个元素建堆


◽ 获取前K个最大数 ,则建小堆。

◽ 获取前K个最小数, 则建大堆


void AdjustDown(int* a, int n, int parent)//实现的前提是左右子树都是堆
{
  int child = parent * 2 + 1;
  while (child < n)
  {
    //选出左右孩子中比较大的孩子,假设child为左边,假设左边孩子比较xiao
    if (child + 1 < n && a[child] > a[child + 1])//不过这里存在越界的风险,不能保证右边的孩子一定存在
    {//右边的孩子要存在的话也需要小于n才可以所以我们再加上去
      ++child;//让右边的孩子成为比较大的child
    }
    //然后让根(父亲)与较大儿子比较,这里是大堆,父亲要大于儿子的
    if (a[parent] >a[child])
    {
      Swap(&a[parent], &a[child]);
      //交互完后,让parent跳到儿子位置上去,儿子继续往下找
      parent = child;
      child = parent * 2 + 1;
    }
    else
    {
      break;
    }
  }
}


// 1. 建堆--用a中前k个元素建堆
  for (int i = (k - 2) / 2; i >= 0; i--)
  {
    AdjustDown(a, k, i);
  }


2.用剩余的n-k个数据与堆顶元素比较,不满足则替代入堆


将剩下的n-k数据依次比较完后,堆里的数据就是所求的前K个最大或最小的元素。


如果我们需要前K个最大数,那要求建小堆,为什么呢?


小堆堆顶数据应该是比较小的数据,而当剩下的n-k个数据与堆顶数据比较时,当数据大于堆顶数据时,就代替它入堆,入堆后,根据堆的性质,这个数就会往堆的下面走,而堆顶数据则是更新为比这个数据较小的值。


接着进行比较,当数据都比较完时,堆里的数据就是我们想要的前K个最大数。


因为当有一个较大数过来与堆顶比较时,它肯定会替代堆顶元素然后入堆,入完堆后,就会在堆里向下调整,因为这是个小堆,要求父节点值小于子结点,所以它肯定会在下面。并且如果这个值是最大值,它就会跑到堆的最后面去。次大的数就会跑到堆的尾部位置,在最大的前面。


就是根据这个原理,我们可以选出前K个最大数。


选出前K个最小数,原理也相似,建个大堆即可。


首先将大堆的图形想象出来,大堆上面较大,下面较小。


当一开始从数据集合中选出K个建堆后,剩下的n-k开始与堆顶元素比较


当剩下的数据比堆顶元素小时,就代替堆顶数据入堆,入堆的这个数据就会往下走,某个比它大的值就变成堆顶元素,接着比较,如果来了一个最小值,则它必然会替代堆顶元素入队,并且入堆后,就会向下调整到堆的最后面前。


// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
  for (int i = k; i < n; i++)
  {
    if (a[i] > a[0])
    {
      a[0] = a[i];
      AdjustDown(a, k, 0);
    }
  }
  for (int i = 0; i < k; i++)
  {
    printf("%d ", a[i]);
  }


③.快速测试代码


我们可以生成一个数组,这个数组拥有10000个数字,每个数字都不超过1000000,然后我们想要获取前10最大的数,拿这个例子来测试代码,只要我们随便改变数组中10个值,让它大于1000000,那得到的结果肯定就是这些值了,这样可以做到快速测试代码。


#include <time.h>
void PrintTopK(int* a, int n, int k)
{
  // 1. 建堆--用a中前k个元素建堆
  for (int i = (k - 2) / 2; i >= 0; i--)
  {
    AdjustDown(a, k, i);
  }
  // 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
  for (int i = k; i < n; i++)
  {
    if (a[i] > a[0])
    {
      a[0] = a[i];
      AdjustDown(a, k, 0);
    }
  }
  for (int i = 0; i < k; i++)
  {
    printf("%d ", a[i]);
  }
}
void TestTopk()
{
  int n = 10000;
  int* a = (int*)malloc(sizeof(int) * n);
  srand(time(0));
  for (size_t i = 0; i < n; ++i)
  {
    a[i] = rand() % 1000000;//生成1000000以内的随机数
  }
  a[5] = 1000000 + 1;//随机改动10个值,让这几个值都大于1000000
  a[1231] = 1000000 + 2;
  a[531] = 1000000 + 3;
  a[5121] = 1000000 + 4;
  a[115] = 1000000 + 5;
  a[2335] = 1000000 + 6;
  a[9999] = 1000000 + 7;
  a[76] = 1000000 + 8;
  a[423] = 1000000 + 9;
  a[3144] = 1000000 + 10;
  PrintTopK(a, n, 10);//最后获得前K个最大的数字就是我们改动的数字
}
int main()
{
  TestTopk();
  return 0;
}


21fd8feded354a96990bc90e80fa725f.png

相关文章
|
存储 BI 数据库
PowerApps教程-实现简单的增删改查
PowerApps是Microsoft提供的低代码开发平台,允许用户无需编写大量代码,通过直观的界面设计快速创建应用程序。通过PowerApps的数据连接功能,系统可以轻松地与其他Microsoft 365服务(如SharePoint、Excel)进行集成,实现数据的无缝交互。本文详细介绍了如何使用PowerApps快速开发一个支持增删改查的报表页面,采用SharePoint上的List作为数据源。
569 0
|
4月前
|
供应链 监控 数据可视化
如何开发采购供应链管理系统中的采购管理看板(附架构图+流程图+代码参考)
在现代企业中,采购管理是供应链管理的关键环节,直接影响成本控制与产品质量。随着企业信息化发展,传统手工管理已无法满足需求,采购管理看板应运而生。它通过可视化展示采购流程各环节,实现进度监控与数据分析,提升采购效率、降低成本,增强供应链响应能力。本文详解其功能设计、业务流程、开发技巧及应用效果,助力企业构建高效采购管理系统。
|
前端开发 搜索推荐 Java
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
|
消息中间件 缓存 算法
kafka(三)
kafka(三)
|
Arthas Java 测试技术
JVM —— 类加载器的分类,双亲委派机制
类加载器的分类,双亲委派机制:启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器;JDK8及之前的版本,JDK9之后的版本;什么是双亲委派模型,双亲委派模型的作用,如何打破双亲委派机制
JVM —— 类加载器的分类,双亲委派机制
|
监控 Java 数据库
Spring事务中的@Transactional注解剖析
通过上述分析,可以看到 `@Transactional`注解在Spring框架中扮演着关键角色,它简化了事务管理的复杂度,让开发者能够更加专注于业务逻辑本身。合理运用并理解其背后的机制,对于构建稳定、高效的Java企业应用至关重要。
440 0
|
SQL 缓存 关系型数据库
数据库连接池到底应该设多大?
数据库连接池到底应该设多大?
621 0
|
监控 安全 Java
JVM工作原理与实战(三十八):JIT即时编译器原理
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了JIT即时编译器、HotSpot中的JIT编译器、JIT优化技术、JIT优化建议等内容。
396 0
|
Java 应用服务中间件 数据库
Java:企业级java后端开发,需要掌握哪些内容
Java:企业级java后端开发,需要掌握哪些内容
1867 1
|
Java 数据安全/隐私保护
【面试问题】JDK 动态代理与 CGLIB 区别?
【1月更文挑战第27天】【面试问题】JDK 动态代理与 CGLIB 区别?