[数据结构 -- C语言] 堆实现Top-K问题,原来王者荣耀的排名是这样实现的,又涨知识了

简介: [数据结构 -- C语言] 堆实现Top-K问题,原来王者荣耀的排名是这样实现的,又涨知识了

TopK问题的引入:

大家在玩王者荣耀的时候都遇到过xxx市第xxx某英雄,xxx区第xxx某英雄。或者是今天我们点外卖的时候想吃某个食物,我们打开美团/饿了么,选离自己最近的选项或者评分最高的选项就会将你所选的店铺的前x名按顺序排出来。福布斯排行榜前10名,胡润富豪排行榜前5名等等。这些问题都是需要对大量的数据排序,选出最大的前K个,这里就用到了TopK算法来解决这一类问题。

1、什么是Top-K问题?

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能

数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

1.1 Top-K基本思路

(1)用数据集合中前K个元素来建堆

       如果是前k个最大的元素,则建小堆

       如果是前k个最小的元素,则建大堆
(2)用剩余的N-K个元素依次与堆顶元素来比较(我们这里求前K个最大为例),我们是建小堆,所以堆顶元素是这个小堆中最小的,因此我们就从剩下的N-K个元素第一个开始与堆顶比较,如果大于堆顶元素,就将堆顶元素替换掉,并向下调整重建小堆,如果小于堆顶元素就不替换,让下一个元素与堆顶比较,剩下的N-K个元素依次比较,重复此步骤。

(3)将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

2、Top-K问题逻辑分析

(1)先用前K个建小堆;

(2)将剩余的N - K 个元素依次与堆顶元素比较,大于就替换;

(3)打印堆。

这是我们的大逻辑,我们将这三步一步步的来分析:

2.1 建堆,大小为K的小堆

过程:

1.我们先开辟一个大小为k的空间;

2.将前K个数据向下调整建成小堆。(向下调整建堆不明白的小伙伴可以戳这里复习

代码如下:

int* kminheap = (int*)malloc(sizeof(int) * k);
if (NULL == kminheap)
{
    perror("malloc fail:");
    return;
}
for (int i = 0; i < k; i++)
{
    fscanf(fout, "%d", &kminheap[i]);
}
//建小堆
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
    AdjustDown(kminheap, k, i);
}

2.2 将剩余的N - K 个元素依次与堆顶元素比较,大于就替换

过程:
1.因为我们是前K个最大数据,所以我们建的是小堆,小堆的堆顶元素就是这个堆中最小的元素,让剩下的N-K个元素依次与堆顶比较;


2.如果这个元素比堆顶大,我们就让它替换掉堆顶元素,如果小于则不交换,依次往后面的元素走再去比较;


3.如果交换了,就从堆顶开始往下调整重新建堆,堆顶就又是最小的元素;


4.当 N-K 个元素依次比较完后,堆中的 K 个元素就是要找的前 K 个最大元素。

代码如下:

int val = 0;
while (!feof(fout))
{
    fscanf(fout, "%d", &val);
    if (val > kminheap[0])
    {
        kminheap[0] = val;
        AdjustDown(kminheap, k, 0);
    }
}

2.3 打印堆

for (int i = 0; i < k; i++)
{
    printf("%d ", kminheap[i]);
}

3、TopK实现代码

void PrintTopK(int k)
{
  const char* file = "data.txt";
  FILE* fout = fopen(file, "r");
  if (NULL == fout)
  {
    perror("fopen error:");
    return;
  }
    int* kminheap = (int*)malloc(sizeof(int) * k);
    if (NULL == kminheap)
    {
      perror("malloc fail:");
      return;
    }
    for (int i = 0; i < k; i++)
    {
      fscanf(fout, "%d", &kminheap[i]);
    }
    //建小堆
    for (int i = (k - 1 - 1) / 2; i >= 0; i--)
    {
      AdjustDown(kminheap, k, i);
    }
  int val = 0;
  while (!feof(fout))
  {
    fscanf(fout, "%d", &val);
    if (val > kminheap[0])
    {
      kminheap[0] = val;
      AdjustDown(kminheap, k, 0);
    }
  }
  for (int i = 0; i < k; i++)
  {
    printf("%d ", kminheap[i]);
  }
  printf("\n");
}

我们这里的代码是从文件中读数据的,我们是将准备的数据存放在文件中。

4、Top-K问题完整代码

我们这是先造1000个数字,将数字存放到一个文件中,求 Top-K 的时候再从文件中拿这些数字。

void CreateNData()
{
  //造数据
  int n = 1000;
  srand((unsigned int)time(NULL));
  const char* file = "data.txt";
  FILE* fin = fopen(file, "w");
  if (NULL == fin)
  {
    perror("fopen error:");
    return;
  }
  for (size_t i = 0; i < n; i++)
  {
    int x = rand() % 100000;
    fprintf(fin, "%d\n", x);
  }
  fclose(fin);
}
void PrintTopK(int k)
{
  const char* file = "data.txt";
  FILE* fout = fopen(file, "r");
  if (NULL == fout)
  {
    perror("fopen error:");
    return;
  }
    int* kminheap = (int*)malloc(sizeof(int) * k);
    if (NULL == kminheap)
    {
      perror("malloc fail:");
      return;
    }
    for (int i = 0; i < k; i++)
    {
      fscanf(fout, "%d", &kminheap[i]);
    }
    //建小堆
    for (int i = (k - 1 - 1) / 2; i >= 0; i--)
    {
      AdjustDown(kminheap, k, i);
    }
  int val = 0;
  while (!feof(fout))
  {
    fscanf(fout, "%d", &val);
    if (val > kminheap[0])
    {
      kminheap[0] = val;
      AdjustDown(kminheap, k, 0);
    }
  }
  for (int i = 0; i < k; i++)
  {
    printf("%d ", kminheap[i]);
  }
  printf("\n");
}

结果展示:

快速验证技巧:我们这里是写到文件里面了,我们为了快速验证代码是否写正确调用一遍生成数据的接口,然后将它注释掉,进到data.txt文件中改五个最大的数据出来,再去打印,这样就能快速验证。


对比两张图片,打印出来的就是前 5 个最大的值。

*** 本篇完 ***

相关文章
|
23天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
34 1
|
1月前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
45 4
|
1月前
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
43 4
|
1月前
|
存储 算法 数据处理
【趣学C语言和数据结构100例】81-85
本文介绍了五个经典算法问题及其C语言实现,涵盖图论与树结构的基础知识。包括使用BFS求解单源最短路径、统计有向图中入度或出度为0的点数、统计无向无权图各顶点的度、折半查找及二叉排序树的查找。这些算法不仅理论意义重大,且在实际应用中极为广泛,有助于提升编程能力和数据结构理解。
39 4
|
1月前
|
算法 数据可视化 数据建模
【趣学C语言和数据结构100例】76-80
本文介绍了五种图论算法的C语言实现,涵盖二叉树的层次遍历及广度优先搜索(BFS)和深度优先搜索(DFS)的邻接表与邻接矩阵实现。层次遍历使用队列按层访问二叉树节点;BFS利用队列从源节点逐层遍历图节点,适用于最短路径等问题;DFS通过递归或栈深入图的分支,适合拓扑排序等场景。这些算法是数据结构和算法学习的基础,对提升编程能力和解决实际问题至关重要。
48 4
|
1月前
|
存储 算法 vr&ar
【趣学C语言和数据结构100例】71-75
本文介绍了五个C语言数据结构问题及其实现,涵盖链表与二叉树操作,包括按奇偶分解链表、交换二叉树左右子树、查找节点的双亲节点、计算二叉树深度及求最大关键值。通过递归和遍历等方法,解决了理论与实际应用中的常见问题,有助于提升编程能力和数据结构理解。
37 4
|
1月前
|
存储 算法 C语言
【趣学C语言和数据结构100例】66-70
本书《趣学C语言和数据结构100例》精选了5个典型的数据结构问题及C语言实现,涵盖链表与数组操作,如有序集合的集合运算、有序序列表的合并、数组中两顺序表位置互换、三递增序列公共元素查找及奇偶数重排。通过详细解析与代码示例,帮助读者深入理解数据结构与算法设计的核心思想,提升编程技能。
33 4
|
24天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
45 5
|
23天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
54 1
|
1月前
|
存储 机器学习/深度学习 算法
【趣学C语言和数据结构100例】61-65
本文介绍了五个关于C语言和数据结构的经典问题及其实现方法,涵盖查找链表共同后缀、删除重复节点、重新排列链表元素、合并有序链表以及特定条件下的链表排序。每个问题通过具体的算法设计,不仅展示了链表操作的灵活性,还强调了效率优化的重要性。通过这些问题的探讨,读者可以深入理解链表的基本操作和高级应用,提升解决实际问题的能力。
46 4