Topk 问题描述

从海量数据中寻找最大(或最小)的 k 个元素,这类问题被称为 Topk 问题。这个问题无论在实际应用还是面试都会被问到。那我们今天就来看看到底有几种解决方案,以及各个方案的优劣情况。以下解题思路的前提条件是:从数组array[1, n]中,寻找出最大的 k 个数。

全局排序

面对Topk问题,最容易想到的办法就是排序了。将array里的元素进行排列,便可以获得最大的 k 个数。此时,Topk问题就转变成了排序问题,解决Topk问题的时间复杂度变成了排序的时间复杂度。如果使用快排进行排序,那么该问题的时间复杂度就是O(n*lgn)。

局部排序

由于是寻找Topk,所以没有必要对所有的数据都进行排序。虽然快排的表现较好,但是如果使用冒泡或简单选择排序,只需要完成k次排序操作就可以解决问题,即如下图所示。此时的时间复杂度是O(n*k)。注意:局部排序所耗费的时间受 k 值影响。

思路:遍历整个数组,在遍历的过程中利用小根堆记录当前的Topk元素。因为小根堆的最小的元素在堆顶,如果下一个元素大于堆顶元素值,那么它就能入选当前的Topk。关于堆的概念和代码可以看看这篇文章:通俗易懂的讲解堆排序(含Gif图)

例如:从list[ n ] = {58, 32, 73, 20, 31, 95, 46, 77, 22, 67,..., n}中寻找最大的5个数。首先利用前5个元素建立堆,然后再与后续的元素进行对比,不断的和堆顶元素进行对比。若元素大于堆顶元素,则进行替换,然后调整堆,使其一直保持小根堆的性质。所有元素比对完成后,堆中的元素就是该序列中最大的5个数。

初始建堆:

使用95与堆顶元素对比,若大于堆顶元素,则替换:

替换95后需要调整堆:

重复上述操作,直至所有元素比对完成,堆中的元素就是所求的最大的 5个元素。

代码实现

#include <iostream>
#include <time.h>
#include <stdlib.h>
#include <cstdlib>
#include <stdio.h>
#include <math.h>
#define M 1000000
#define K 50
using namespace std;template <class T>
void Print(T a[], int n, int m)
{for(int i = n; i < m; i++){cout << "[" << a[i] << "]";}cout <<endl;
}template <class T>
void Swap(T &a, T &b)
{T asd;asd = a;a = b;b = asd;
}template <class T>
int Partition(T a[], int p, int r)
{int i = p, j = r+1;T x = a[p];while(true){while(a[++i] < x && i < r);while(a[--j] > x);if(i >= j)break;Swap(a[i], a[j]);}a[p] = a[j];a[j] = x;return j;
}template <class T>
void QuickSort(T a[], int p, int r)
{if(p < r){int q = Partition(a, p, r);QuickSort(a, p, q-1);QuickSort(a, q+1, r);}
}void test(int a[])
{int i,temp;for(i = 0; i < M; ++i){if(a[i] > i)temp = 1;elsetemp = 0;}
}void BubbleSort(int a[])
{int i,j,flag,temp;for(i = 0; i < K; ++i){flag = 0;for(j = 0; j < M-i-1; ++j){if(a[j] > a[j+1]){temp = a[j];a[j] = a[j+1];a[j+1] = temp;flag = 1;}}if(flag == 0) break;    }
}void BubbleSort1(int a[], int n)
{int i,j,flag,temp;for(i = 0; i < n; ++i){flag = 0;for(j = 0; j < n-i-1; ++j){if(a[j] > a[j+1]){temp = a[j];a[j] = a[j+1];a[j+1] = temp;flag = 1;}}if(flag == 0) break;    }
}void heap_ajust_min(int *b, int i, int size)  /*a为堆存储数组,size为堆的大小*/
{int lchild = 2*i;       //i的左孩子节点序号 int rchild = 2*i +1;     //i的右孩子节点序号 int min = i; /*存放三个顶点中最大的数的下标*/int temp;if(i <= size/2)          //假设i是叶节点就不用进行调整 {if(lchild<=size && b[lchild]<b[min]){min = lchild;}    if(rchild<=size && b[rchild]<b[min]){min = rchild;}if(min != i){temp = b[i]; /*交换a[i]和a[max]的值*/b[i] = b[min];b[min] = temp;heap_ajust_min(b, min, size); /*被交换的位置曾经是大根堆,如今可能不是大根堆所以须要又一次调整使其成为大根堆结构*/ }}
}void build_bheap_min(int *b, int size) /*建立小堆*/
{int i;for(i=size/2; i >= 1; i--) /*非叶节点最大序号值为size/2*/{heap_ajust_min(b, i, size); /*每一个非叶子结点都须要调用这个函数*/   }
}int a[M] = {0};
int a1[M] = {0};
int a2[M] = {0};
int a3[M] = {0};
int b[K+1]={0};
int c[K] = {0};int main()
{srand(time(0));for(int i = 0; i < M; i++){a[i] = rand()%(M);       //设置随机数组a1[i] = rand()%(M);a2[i] = rand()%(M);a3[i] = rand()%(M);}//方法一clock_t start_time = clock();QuickSort(a, 0, M-1);clock_t end_time = clock();cout<<"快排全排列:"<<static_cast<double>(end_time - start_time)/CLOCKS_PER_SEC*1000<<"ms"<<endl;//方法二clock_t start_time1 = clock();BubbleSort(a1);clock_t end_time1 = clock();cout<<"冒泡排列k次:"<<static_cast<double>(end_time1 - start_time1)/CLOCKS_PER_SEC*1000<<"ms"<<endl;//方法三clock_t start_time2 = clock();for(int i = 0; i < M; ++i){if(i < K){c[i] = a2[i];}  else{if(a2[i] > c[0]){c[0] = a2[i];BubbleSort1(c, K);}}}clock_t end_time2 = clock();cout<<"使用冒泡方法记录Topk:"<<static_cast<double>(end_time2 - start_time2)/CLOCKS_PER_SEC*1000<<"ms"<<endl;//方法四clock_t start_time3 = clock();for(int i = 0; i < M; ++i){if(i <= K){b[i+1] = a3[i];}   else{if(a3[i] > b[1]){b[1] = a3[i];build_bheap_min(b, K);}}}clock_t end_time3 = clock();cout<<"使用小根堆记录Topk:"<<static_cast<double>(end_time3 - start_time3)/CLOCKS_PER_SEC*1000<<"ms"<<endl;return 0;
}

实验

在100万个元素的情况下,分别令 K 的值为10和50。实验结果如下:

可以发现冒泡排序受 K 的变化而变化,而其他的三种方式较为稳定。四种方法中表现较好的是后两种,为了比个高低,我设置了1000万个元素,分别令K值为1000和10000,实验结果如下:

在扩充了元素和改变 K 值后可发现,利用小根堆的方法性能更好。原因:假设在最坏的情况下,方法三每次完成一次排序,方法四每次都要调整堆。方法三的时间复杂度为O(n*k),而方法四的时间复杂度为O(n*lg(k))。

Topk 问题详解及代码和数据分析相关推荐

  1. Pandas获取SQL数据库read_sql()函数及参数一文详解+实例代码

    前言 Pandas常用作数据分析工具库以及利用其自带的DataFrame数据类型做一些灵活的数据转换.计算.运算等复杂操作,但都是建立在我们获取数据源的数据之后.因此作为读取数据源信息的接口函数必然拥 ...

  2. 调包侠福音!机器学习经典算法开源教程(附参数详解及代码实现)

    Datawhale 作者:赵楠.杨开漠.谢文昕.张雨 寄语:本文针对5大机器学习经典算法,梳理了其模型.策略和求解等方面的内容,同时给出了其对应sklearn的参数详解和代码实现,帮助学习者入门和巩固 ...

  3. 粒子群(pso)算法详解matlab代码,粒子群(pso)算法详解matlab代码

    粒子群(pso)算法详解matlab代码 (1)---- 一.粒子群算法的历史 粒子群算法源于复杂适应系统(Complex Adaptive System,CAS).CAS理论于1994年正式提出,C ...

  4. 图像质量损失函数SSIM Loss的原理详解和代码具体实现

    本文转自微信公众号SIGAI 文章PDF见: http://www.tensorinfinity.com/paper_164.html http://www.360doc.com/content/19 ...

  5. python 自动化-Python API 自动化实战详解(纯代码)

    主要讲如何在公司利用Python 搞API自动化. 1.分层设计思路 dataPool :数据池层,里面有我们需要的各种数据,包括一些公共数据等 config :基础配置 tools : 工具层 co ...

  6. 数学建模——智能优化之遗传算法详解Python代码

    数学建模--智能优化之遗传算法详解Python代码 import numpy as np import matplotlib.pyplot as plt from matplotlib import ...

  7. 数学建模——主成分分析算法详解Python代码

    数学建模--主成分分析算法详解Python代码 import matplotlib.pyplot as plt #加载matplotlib用于数据的可视化 from sklearn.decomposi ...

  8. 数学建模——智能优化之模拟退火模型详解Python代码

    数学建模--智能优化之模拟退火模型详解Python代码 #本功能实现最小值的求解#from matplotlib import pyplot as plt import numpy as np imp ...

  9. 数学建模——智能优化之粒子群模型详解Python代码

    数学建模--智能优化之粒子群模型详解Python代码 import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplo ...

最新文章

  1. java 异常类_Java异常处理
  2. 练习:Padding 填充
  3. 初识Docker-Docker的安装
  4. 敏捷开发组织【北京及其他地区QQ群】【长三角QQ群】【珠三角QQ群】
  5. 关闭用playsound函数的WAV文件
  6. mysql datetime类型按天查询_mysql 时间相关sql , 按天、月、季度、年等条件进行查询...
  7. 没有bug队——加贝——Python 练习实例 35,36
  8. 广播 BroadCastReceiver
  9. HTML/CSS 面试总结
  10. 珞珈一号影像辐射定标软件操作方法
  11. php空格占几个字符,一个空格几个字符?
  12. 3d打印路径规划 matlab,基于FDM技术的3D打印路径规划技术研究
  13. Android resource compilation failed 一定能搞定的办法
  14. python从入门到精通-张子夜-专题视频课程
  15. 六、v4l2 ctrl 函数初始化---增加自定义接口v4l2_ctrl_new_custom
  16. 便签文档储存位置在哪?便签保存在哪个文件夹,怎么在文件管理找到
  17. 【matlab】常微分方程的数值解法
  18. oracle 查询指定时间范围
  19. 旋转编码器EC11调试心得
  20. 小米平板4软件提取包_小米三大法宝:软件、硬件和服务;以及它的4条成功经验|小米手机|智能手机|手机...

热门文章

  1. 用 70 行代码给你自己写一个 strace
  2. 如何获取Google地图API密钥?(翻译版)
  3. 微服务下分布式事务模式的详细对比
  4. 即构科技金健忠:回顾20年音视频技术演进
  5. 熊猫TV直播H5播放器架构探索
  6. C/C++学习之路_七: 内存管理
  7. 升级 Vue3 大幅提升开发运行效率
  8. 腾讯AI Lab造出中国第一台临床应用智能显微镜!
  9. 裸眼 3D 是什么效果?
  10. st(state-threads) coroutine和stack分析