`

基于堆 [Heap] 结构的 TopK 问题实现

阅读更多

在实际工作中我们经常会遇到将一个list中最大[最小]的前TopK个元素输出的问题。

比如说在电商领域,求上个月卖的最好的前10个商品,或者是每个品类下卖的最好的前10个商品。

 

这类问题,很多数据库或数据仓库工具可以提供直接的实现,比如第一个问题在mysql中就直接order by + limit就好; 而第二个问题在Hive中也很简单, distribute + order by + rownum + where也可以直接搞定,在Oracle则用 partition 就好。

 

而这些实现的原理都是基于对原list做一次排序,然后在排序结果的基础上取出前TopK个元素。

例如Python中可以这样:

 

a=[2,1,3,8,7,6,4,7,8,0,-1,3,2,6,3,9]
a.sort()
top10 = a[0:10]

 

其中的排序过程使得最后取出的前TopK个元素不仅能满足要求,而且还是排好序的。这似乎是一个非常不错的addtional benifit,不过你除非你确信或可接受由此带来的性能开销,最好不要轻易的接受这种不速的benifit。因为天下没有免费的午餐。。当然,就我见到的应用项目中,99%的做法还真就是这种sort + top的实现。没什么不好,只是他们比较“富裕”,能负担的起这个附加的午餐费而已。

 

不过,当有一天,我也用上述方法来解决我项目中的问题时发现,程序运行的很慢很慢,因为我的数据量很大很大。。。初步估计要运行好几百个小时,这当然是不能忍的。当然也是因为Python这种解释性语言本身比较低效。

 

所以必须换一种语言,顺道也换一种更经济的实现策略,就是本文接下来要介绍的:基于堆的topK实现。

很多同学可能都比较熟悉堆排序,但我们的问题并不需要排序,一旦排序,不管用什么方式,都会重蹈覆辙。

 

让我们重新回顾一下问题:取给定List中的前TopK个最大的元素并输出。

关键点:

      1. topK个最大的元素

      2. 我们并不需要顺序,因此一切涉及到sort的工作都是不必要的。

      3. 要且只要topK个元素,no more needed!!

 

因此,我们虽然用堆,但并不需要将整个list都建成堆,只需要维护一个K个元素的堆即可。

其次,我们的堆并不用来排序,这点很重要!!!

 

具体步骤如下:——求最大的TopK个元素

 

      1. 建一个K个元素的空堆 HP,即一个K个元素的数组

 

      2. 遍历原List,将元素依次压入HP中,在压入过程中:

          2.1 如果压入的个数还不到K个,则直接压入,

          2.2 如果压入之后的个数达到K个,则将该K个元素维护成一个堆结构

          2.3 在压入每个元素之前,如果HP中已经有K个元素,则将新元素与HP的第一个元素比较

                 2.3.1 如果新元素大于HP的第一个元素,则将新元素放在HP的第一个位置,并调整HP堆。

                 2.3.2 如果新元素小于HP的第一个元素,则continue 到 2.

 

遍历完后HP中的K个元素即为List中的前TopK大的元素。

 

具体代码如下:

 

   heap.h   ——heap结构申明

#ifndef HEAP_H
#define HEAP_H

/* ------------------------------
 * Data Define for Heap Structor
 * Oh This is just a Min Heap!!!
 * ------------------------------ */

typedef int (*HEAP_CMP_FN)(void *, void*);

typedef struct _heap {
    int len;
    int size;
    void ** data;
    HEAP_CMP_FN heap_cmp_fn;
} Heap;


/* ---------------------------------------------
 * Interface Function Define for Heap Operation
 * --------------------------------------------- */


Heap * heap_create(int size);
void * heap_add(Heap * hp, void * pdata);
void   heap_free(Heap * hp);

#endif

 

   heap.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "heap.h"

/* ----------------------------------
 * Default Compare Function for Heap
 * ---------------------------------- */
int default_heap_cmp_fn(int* m, int* n){
    int i = *m - *n;
    return i > 0 ? 1 : (i < 0 ? -1 : 0);
}

/* ----------------------------------
 * Create and Return a Heap Structor
 * ---------------------------------- */
Heap * heap_create(int size){
    if (size < 2){
        fprintf(stderr,"too small size no need heap\n");
        return NULL;

    }

    Heap * hp = (Heap*)malloc(sizeof(Heap));
    hp->len = 0;
    hp->size = size;
    hp->data = (void**)malloc(sizeof(void*) * size);
    memset(hp->data,0,sizeof(void*) * size);
    hp->heap_cmp_fn = (HEAP_CMP_FN)&default_heap_cmp_fn;

    return hp;
}


#define HEAP_ADJUST(hp,l) do {                                             \
    int lt = l;                                                            \
    int i  = lt + lt + 1;                                                  \
    int r  = hp->len - 1;                                                  \
    void * t = hp->data[lt];                                               \
    do {                                                                   \
        if ((i<r) && (hp->heap_cmp_fn(hp->data[i], hp->data[i+1]) > 0)){   \
            i += 1;                                                        \
        }                                                                  \
        if (hp->heap_cmp_fn(t,hp->data[i]) <= 0){                          \
            break;                                                         \
        }                                                                  \
        hp->data[lt] = hp->data[i];                                        \
        lt = i;                                                            \
        i += i + 1;                                                        \
    }while (i <= r);                                                       \
    hp->data[lt] = t;                                                      \
} while (0);                                                               \


/* ----------------------------------
 * Add an Element into the Heap
 * ---------------------------------- */
void * heap_add(Heap * hp, void * pdata){
    if (hp->len < hp->size){
        hp->data[hp->len] = pdata;
        hp->len += 1;
        if (hp->len >= hp->size){
            int l = hp->len / 2;
            while(--l >= 0){
                HEAP_ADJUST(hp,l);
            }
        }
        return NULL;
    }
    void * top = hp->data[0];
    if (hp->heap_cmp_fn(top,pdata) < 0){
        hp->data[0] = pdata;
        HEAP_ADJUST(hp,0);
        return top;
    }
    return pdata;
}


/* ----------------------------------
 * Free the Heap Memory Space
 * ---------------------------------- */
void   heap_free(Heap * hp){
    if (hp->data){
        for (int i = 0; i < hp->len; i++){
            free(hp->data[i]);
            hp->data[i] = NULL;
        }
        free(hp->data);
        hp->data = NULL;
    }
    free(hp);
}

 

   heap_test.c

#include <stdlib.h>
#include <stdio.h>
#include "heap.h"

//  int main(){
//      Heap * hp = heap_create(10);
//      for (int i = 0; i < 20; i++){
//          int c = rand() % 100;
//          printf("%d,",c);
//          int * v = (int*)malloc(sizeof(int));
//          *v = c;
//          int * p = heap_add(hp,v);
//          if (p)
//              free(p);
//      }
//      printf("\n");
//      for (int i = 0;i < hp->len;i++){
//          printf("%d,",*((int*)hp->data[i]));
//      }
//      printf("\n");
//      return 0;
//  }

typedef struct {
    int id;
    float score;
} Element;


int comp(Element * m,Element * n){
    if (m->score > n->score){
        return 1;
    }
    else if (m->score<n->score){
        return -1;
    }
    else{
        return 0;
    }
}

int main(){
    Heap * hp = heap_create(5);
    hp->heap_cmp_fn = (HEAP_CMP_FN)&comp;
    for (int i = 0; i < 20; i++){
        int id = rand() % 100;
        float score = 100.0 * (float)rand() / RAND_MAX ;
        Element * p = (Element*)malloc(sizeof(Element));
        p->id = id;
        p->score = score;
        printf("%d,%f\n",id,score);
        Element * t = heap_add(hp,p);
        if (t)
            free(t);
    }
    for (int i = 0;i < hp->len;i++){
        fprintf(stderr,"%d,%f\n",((Element *)hp->data[i])->id,((Element *)hp->data[i])->score);
    }
    return 0;
}

 

如代码所示,该Heap结构默认支持int类型的数据,如果元素类型为其他,则需要提供一个定制的比较函数,赋值给堆结构的 heap_cmp_fn 函数指针即可,如测试代码中:

hp->heap_cmp_fn = (HEAP_CMP_FN)&comp; // comp为定制比较函数

 

 

 

 

分享到:
评论

相关推荐

    基于springboot教育资源共享平台源码数据库文档.zip

    基于springboot教育资源共享平台源码数据库文档.zip

    视频笔记linux开发篇

    linux开发篇,配套视频:https://www.bilibili.com/list/474327672?sid=4493702&spm_id_from=333.999.0.0&desc=1

    readera-24-09-08plus2020.apk

    ReadEra 这个阅读应用能够打开下列任何格式的文档: EPUB, PDF, DOC, RTF, TXT, DJVU, FB2, MOBI, 和 CHM. 基本上来说,你可以用它阅读你的设备内存中的任何书籍或者文本文档。 这个应用与划分成章节的文档兼。,有一个书签功能,可以在你阅读的时候,自动保存你的进度。另外,它让你更改页面模式,从几种不同的主题中进行挑选(夜间,白天,棕黑色调,还有控制台)。

    STM32单片机控制舵机旋转

    软件环境:KEIL4 硬件环境:STM32单片机+舵机 控制原理:通过控制输出信号的占空比调节舵机旋转的角度

    基于springboot仓库管理系统源码数据库文档.zip

    基于springboot仓库管理系统源码数据库文档.zip

    酒店管理系统源码C++实现的毕业设计项目源码.zip

    酒店管理系统源码C++实现的毕业设计项目源码.zip,个人大四的毕业设计、经导师指导并认可通过的高分设计项目,评审分98.5分。主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。 酒店管理系统源码C++实现的毕业设计项目源码.zip,酒店管理系统源码C++实现的毕业设计项目源码.zip个人大四的毕业设计、经导师指导并认可通过的高分设计项目,评审分98.5分。主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。酒店管理系统源码C++实现的毕业设计项目源码.zip酒店管理系统源码C++实现的毕业设计项目源码.zip酒店管理系统源码C++实现的毕业设计项目源码.zip,个人大四的毕业设计、经导师指导并认可通过的高分设计项目,评审分98.5分。主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。酒店管理系统源码C++实现的毕业设计项目源码.zip,个人大四的毕业设计、经导师指导并认可通过的高分设计项目,评审分98.5分。主要针对计算机相关专业的正在做毕

    58商铺全新UI试客试用平台网站源码

    58商铺全新UI试客试用平台网站源码

    基于SpringBoot+Vue的轻量级定时任务管理系统.zip

    springboot vue3前后端分离 基于SpringBoot+Vue的轻量级定时任务管理系统.zip

    毕业设计&课设_微博情感分析,用 flask 构建 restful api,含相关算法及数据文件.zip

    该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

    4D毫米波雷达点云数据处理方法研究.caj

    4D毫米波雷达点云数据处理方法研究.caj

    S M 2 2 5 8 X T量产工具

    S M 2 2 5 8 X T 量产工具供大家下载使用

    基于springboot的文物管理系统源码数据库文档.zip

    基于springboot的文物管理系统源码数据库文档.zip

    基于springboot的电影院售票管理系统源码数据库文档.zip

    基于springboot的电影院售票管理系统源码数据库文档.zip

    Javaweb仓库管理系统项目源码.zip

    基于Java web 实现的仓库管理系统源码,适用于初学者了解Java web的开发过程以及仓库管理系统的实现。

    美容美发项目,使用django框架,前后端一体化项目

    美容美发项目,使用django框架,前后端一体化项目

    2023年中国在线票务行业市场规模约为24.99亿元,挖掘市场新机遇

    在线票务:2023年中国在线票务行业市场规模约为24.99亿元,挖掘市场蓝海新机遇 在数字浪潮的席卷下,传统的票务销售模式正经历着前所未有的变革。纸质门票逐渐淡出人们的视野,取而代之的是便捷、高效的数字和移动票务。这一转变不仅为消费者带来了前所未有的购票体验,更为在线票务平台开辟了广阔的发展空间和市场机遇。随着国民经济的持续增长和文体娱乐行业的蓬勃发展,中国在线票务行业正站在时代的风口浪尖,等待着每一位有志之士的加入。那么,这片蓝海市场究竟蕴藏着怎样的潜力?又该如何把握机遇,实现突破?让我们一同探索。 市场概况: 近年来,中国在线票务行业市场规模持续扩大,展现出强劲的增长势头。据QYResearch数据显示,2023年中国在线票务行业市场规模约为24.99亿元,尽管受到宏观经济的影响,市场规模增速放缓,但整体趋势依然向好。这一增长主要得益于国民人均收入的不断提高、电影及演出行业的快速发展以及政府政策的支持。例如,2023年财政部、国家电影局发布的《关于阶段性免征国家电影事业发展专项资金政策的公告》,为电影行业注入了强劲动力,进而推动了在线票务市场规模的扩大。 技术创新与趋势: 技术进步

    基于SpringBoot的养老院管理系统源码数据库文档.zip

    基于SpringBoot的养老院管理系统源码数据库文档.zip

    毕业设计&课设_含构建设置及相关操作,基于特定技术,具体功能未详细说明.zip

    该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

    Go语言入门指南:基础语法、并发编程详解

    内容概要:本文档是一份详细的Go语言教程,从基础概念介绍到高级主题均有覆盖。主要内容包括Go语言的基础语法、数据类型、控制结构、函数、结构体、接口和并发编程等方面。通过具体示例介绍了如何使用Go语言进行开发。 适合人群:初学者和有一定经验的程序员都可以从这篇教程中受益,特别是那些想要快速掌握Go语言并应用于实际项目的开发者。 使用场景及目标:适用于初学者系统学习Go语言的基础知识和常用功能;也可以作为已有开发经验者的参考资料,帮助他们解决具体的编程问题,提高开发效率。 其他说明:本教程不仅包含了Go语言的基本知识点,还重点讲解了其独特的并发编程模型。读者在学习过程中应该注重理论与实践相结合,通过实际编写代码来加深理解和记忆。

    基于springboot计算机基础网上考试系统源码数据库文档.zip

    基于springboot计算机基础网上考试系统源码数据库文档.zip

Global site tag (gtag.js) - Google Analytics