top-K问题详解

           top-K 问题是一类经典的问题,它能解决许多海量数据处理相关的问题,例如在1亿个ip中找出访问次数前1000的ip,在海量搜索字符串中找出搜索频率排在前十的搜索字符串等等。下面我们由浅入深对其进行分析。

我们可以将这类问题分为三个方向考虑:

        1.将输入内容(假设用数组存放)进行完全排序,从中选出排在前K的元素即为所求。有了这个思路,我们可以选择相应的排序算法进行处理,目前来看快速排序,堆排序和归并排序都能达到O(nlogn)的时间复杂度。

        2.对输入内容进行部分排序,即只对前K大的元素进行排序(这K个元素即为所求)。此时我们可以选择冒泡排序或选择排序进行处理,即每次冒泡(选择)都能找到所求的一个元素。这类策略的时间复杂度是O(Kn)。

        3.对输入内容不进行排序,显而易见,这种策略将会有更好的性能开销。我们此时可以选择两种策略进行处理:

                a)利用小根堆维护一个大小为K的数组,目前该小根堆中的元素是排名前K的数,其中根是最小的数。此后,每次从原数组中取一个元素与根进行比较,如大于根的元素,则将根元素替换并进行堆调整(下沉),即保证小根堆中的元素仍然是排名前K的数,且根元素仍然最小;否则不予处理,取下一个数组元素继续该过程。该算法的时间复杂度是O(nlogK),一般来说企业中都采用该策略处理top-K问题,因为该算法不需要一次将原数组中的内容全部加载到内存中,而这正是海量数据处理必然会面临的一个关卡。

                b)利用快速排序的分划函数找到分划位置K,则其前面的内容即为所求。该算法是一种非常有效的处理方式,时间复杂度是O(n)(证明可以参考算法导论书籍)。对于能一次加载到内存中的数组,该策略非常优秀。


        下面给出采用分划函数进行处理的代码:

import java.util.Scanner;

public class Main {
	
	public static void topK(int[] a,int k)
	{	
		int len = a.length;
		if(len <= k)	//数组元素个数小于k,则不需要处理
			return ;
		int low = 0;
		int high = len;
		int j = partition(a,low,high);	//找到划分位置j
		while(j!=k)	//划分位置不是k则继续处理
		{
			if(k > j)	//k在分划点后面部分
				low = j+1;
			else
				high = j;	//k在分划点前面部分
			j = partition(a,low,high);
		}
	}
	
	public static int partition(int[] a,int low,int high)	//分划函数
	{
		if(high <= low)
			return low;
		int i=low;
		int j=high;
		while(i<j)
		{
			i++;
			while(i<high && a[i]>a[low])	//从前往后找小于等于a[low]的元素
				i++;
			j--;
			while(j>=low && a[j]<a[low])	//从后往前找大于等于a[low]的元素
				j--;
			if(i<j)	//交换
			{
				int swap = a[i];
				a[i] = a[j];
				a[j] = swap;
			}
		}
		int swap = a[low];
		a[low] = a[j];
		a[j] = swap;
		return j;	//返回分划点
	}
	
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int[] a = new int[n];
		for(int i=0;i<n;i++)	//输入数组
			a[i] = scan.nextInt();
		
		int k = scan.nextInt();	//输入K
		if(n>k)
			topK(a,k);
		if(k>=n)
			k = n;
		for(int i=0;i<k;i++)	//输出前K大的数
			System.out.print(a[i]+" ");
		System.out.println();
	}
}


  • 11
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值