寻找前K大数

原创 2014年02月15日 17:22:33

寻找前K大数虽然这个问题已经被做烂了,但是无意中看到之前A题时候的一个代码,还是忍不住改了改想提高下效率,从O(NlogN)降到O(N+KlogK)。


Problem Description
浙江桐乡乌镇共有n个人,请找出该镇上的前m个大富翁.
 

Input
输入包含多组测试用例.
每个用例首先包含2个整数n(0<n<=100000)和m(0<m<=10),其中: n为镇上的人数,m为需要找出的大富翁数, 接下来一行输入镇上n个人的财富值.
n和m同时为0时表示输入结束.
 

Output
请输出乌镇前m个大富翁的财产数,财产多的排前面,如果大富翁不足m个,则全部输出,每组输出占一行.
 

Sample Input
3 1 2 5 -1 5 3 1 2 3 4 5 0 0
 

Sample Output
5 5 4 3


当时用的是很简单的冒泡和sort,恩,也就是对数组全部元素排序,复杂度O(NlogN), 但是当K很小时,比如题目中K<=10的情况无疑浪费了。因此用quicksort的思路,每次将数组partition成两部分,一边<key,一边>key。然后迭代地找前k大数。


code:

//
//  k_biggest_num.c
//  ACM
//  Find the k biggest number in an array
//
//  Created by Rachel on 14-2-13.
//  Copyright (c) 2014年 ZJU. All rights reserved.
//

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <functional>
#include <utility>
using namespace std;
#define N 100010
#define K 11
typedef int DT;//datatype: DT
DT A[N];
DT kbig[K];
DT partition_r[N],partition_l[N];

void swap(DT* a, DT* b)
{
    DT t = *a;
    *a = *b;
    *b = t;
}

pair<int,int> findKbig(int left, int right, int k)
{
    int l = left, r = right, m = (l+r)/2;
	DT key = A[m];
	while(l<=r)
	{
		while(A[l]<key) l++;
		while(A[r]>key) r--;
		if(l<=r) 
		{
			swap(&A[l],&A[r]); l++; r--;
		}
	}
	/*
	for(int i = left; i<=right; i++)
		cout<<A[i]<<" ";
	cout<<endl;
	*/

	int n = right-l+1; //#elements > key
	pair<int, int> lr = make_pair(l,right);
	if(right-left+1==k)
	{
		lr.first = left;
		return lr;
	}
	else if(n==k)
		return lr;
	else if(n>k)
		return findKbig(l,right,k);
	else
	{
		lr.first = findKbig(left,l-1,k-n).first;
		return lr;
	}
}

int main()
{
    int i,n,k;
    while (true) {
        cin>>n>>k;
        
        if (n==0 && k==0) {
            break;
        }
        k = k>n?n:k;
        for (i=0; i<n; i++) {
            cin>>A[i];
        }
        pair<int,int> P = findKbig(0,n-1,k);
		int l = P.first, r = P.second;
		DT res[K];
		memset(res,0,sizeof(res));
		for(i = l; i<=r ; i++)
			res[i-l] = A[i];
        sort(res, res+K,greater<int>());
        for (i = 0; i<k-1; i++) {
            cout<<res[i]<<" ";
        }
        cout<<res[i]<<endl;
    }
    return 0;
}

some typical testing samples:

6 3

2 4 6 9 1 2


10 3

2 3 4 1 53 23 52 32 32 32


4 2

24 34 24 24


5 6

1 2 3 4 5


5 3 

5 3 1 4 2


5 3

1 2 1 1 1


5 3

1 2 3 4 5


10 4

2 5 3 6 2 7 8 7 4 3



---------------------------------------------------

复杂度:

假设每次partition砍掉一半,则

T(N) = T(N/2) + N

 = T(N/4) + N + N/2

 = ...

 = T(1) + N + N/2 + N/4 + ...


∴T(N) = N + N/2 + N/4 + ... = 2N


再对选出元素排序:O(KlogK)

所以复杂度O(N+KlogK).




法2. 最小堆(size = k)

复杂度O(nlogk)

//
//  k_biggest_num.c
//  ACM
//  Find the k biggest number in an array
//
//  Created by Rachel on 14-2-16.
//  Copyright (c) 2014年 ZJU. All rights reserved.
//

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <functional>
#include <utility>
using namespace std;
#define N 100010
#define K 12
typedef int DT;//datatype: DT
DT kbig[K];//minheap

inline void swap(DT* a, DT* b)
{
    DT t = *a;
    *a = *b;
    *b = t;
}

void AdjustHeap(int idx, int k)
{
	int left = idx*2+1;
	int right = idx*2+2;
	int pos; // the position of smaller child
	if(left>k-1) return;
	else if(right>k-1) pos = left;
	else pos = kbig[left]<kbig[right]?left:right;

	if(kbig[pos]<kbig[idx])
	{
		swap(&kbig[pos],&kbig[idx]);
		AdjustHeap(pos,k);
	}	
}

void BuildHeap(int k)
{
	int nonleaf_idx = (k-2)/2;
	for (int i = nonleaf_idx; i>=0; i--)
		AdjustHeap(i,k);
}

int main()
{
    int i,n,k,tmp;
    while (scanf("%d%d",&n,&k)!=EOF) {
        
        if (n==0 && k==0)
            break;

        k = k>n?n:k;
        for (i=0; i<k; i++)
			scanf("%d",&kbig[i]);
		BuildHeap(k);
		for (i=k; i<n; i++)
		{
			scanf("%d",&tmp);
			if(tmp>kbig[0])
			{
				kbig[0] = tmp;
				AdjustHeap(0,k);//top-down adjust
			}
		}
		sort(kbig,kbig+k,greater<int>());
        for (i = 0; i<k-1; i++)
			printf("%d ",kbig[i]);
        cout<<kbig[i]<<endl;
    }
    return 0;
}




谢谢Swordholy和xindoo的纠错~


关于Algorithms更多的学习资料将继续更新,敬请关注本博客和新浪微博Rachel Zhang




分治法 快排 输出第k大的数

总时间限制: 10000ms 单个测试点时间限制: 1000ms 内存限制: 65536kB 描述 给定一个数组,统计前k大的数并且把这k个数从大到小输出。 输入第一行包含一...
  • u011002533
  • u011002533
  • 2017年02月14日 23:49
  • 748

海量数据中找出前k大数(topk问题)

前两天面试3面学长问我的这个问题(想说TEG的3个面试学长都是好和蔼,希望能完成最后一面,各方面原因造成我无比想去鹅场的心已经按捺不住了),这个问题还是建立最小堆比较好一些。         先...
  • suibianshen2012
  • suibianshen2012
  • 2016年07月23日 10:59
  • 5786

输出前K大的数

关于问题描述给定一个数组,统计前k大的数并且把这k个数从大到小输出。输入第一行包含一个整数n,表示数组的大小。n < 100000。 第二行包含n个整数,表示数组的元素,整数之间以一个空格分开。每个...
  • Chuxin126
  • Chuxin126
  • 2017年02月17日 21:37
  • 1158

输出前k大的数

题目描述给定一个数组包含n个元素,统计前k大的数并且把这k个数从大到小输出。输入第一行是一个正整数n,表示数组的大小。n < 100000。 第二行包含n个整数,表示数组的元素,整数之间以一个空格分...
  • Servon_Lee
  • Servon_Lee
  • 2018年03月13日 22:34
  • 15

从一个序列中获取前K大的数的一种方法

这个方法是利用快速排序的。在快速排序中,得到中间元素(pivot)之后,比较中间元素之前的元素个数和K的大小关系,从而确定后面该往哪个方向继续递归。如果中间元素前面的元素个数等于K,那就停止递归过程;...
  • u014088857
  • u014088857
  • 2015年01月04日 20:22
  • 1363

选择问题-输出第k大的数

#include #include void sqencing(double a[],int n); int selectkmax(double a[],int n,int k); int m...
  • zyjiscainiao
  • zyjiscainiao
  • 2016年07月28日 23:34
  • 641

输出前m大个数,时间复杂度O(n+mlog(m))

#include using namespace std;void swapM(int &a, int &b) { int tmp = a; a = b; b = tmp; }...
  • rwang_1001
  • rwang_1001
  • 2017年08月13日 15:36
  • 284

|NOIOJ|二分快排|7617:输出前k大的数

描述 给定一个数组,统计前k大的数并且把这k个数从大到小输出。 输入 第一行包含一个整数n,表示数组的大小。n 第二行包含n个整数,表示数组的元素,整数之间以一个空格分开。每个整数的绝对值不超...
  • Darost
  • Darost
  • 2016年05月28日 19:22
  • 1027

经典算法题:无序整数数组中找第k大的数

经典问题:写一段程序,找出数组中第k大的数,输出数所在的位置。 【解法一】先排序,然后输出第k个位置上的数 我们先假设元素的数量不大,例如在几千个左右,在这种情况下,那我们就排序一下吧。在这里,快速...
  • wangbaochu
  • wangbaochu
  • 2016年10月27日 21:02
  • 5152

若干个(大量)数字中找前K大/小的元素--数值型

方法一:根据快速排序划分的思想 : (1) 递归对所有数据分成[a,b)b(b,d]两个区间,(b,d]区间内的数都是大于[a,b)区间内的数 ; (2) 对(b,d]重复(1)操作,直到最右边的区间...
  • gogoky
  • gogoky
  • 2016年05月10日 17:01
  • 1865
收藏助手
不良信息举报
您举报文章:寻找前K大数
举报原因:
原因补充:

(最多只允许输入30个字)