java top k_海量数据 Top K 问题的解决方案

本文介绍了两种解决海量数据中 Top K 问题的方法:基于快速排序的 O(n) 算法和最小堆法。快速排序算法在数组上进行操作,适合小规模数据,而最小堆法(如使用 Java 的 PriorityQueue)适用于处理海量数据,时间复杂度为 O(nlogk)。
摘要由CSDN通过智能技术生成

Top K是很常见的一种问题,是指在N个数的无序序列中找出最大的K个数,而其中的N往往都特别大,对于这种问题,最容易想到的办法当然就是先对其进行排序,然后直接取出最大的K个元素就行了,但是这种方法往往是不可靠的,不仅时间效率低而且空间开销大,排序是对所有数都要进行排序,而实际上,这类问题只关心最大的K个数,并不关心序列是否有序,因此,排序实际上是浪费了的很多资源都是没必要的。

13492.html

题目:

输入 n 个整数,找出其中最大的 k 个数。例如输入4、5、1、6、2、7、3、8 这8个数字,则最大的4个数字是5、6、7、8。

解法一:基于快排的O(n)的算法

如果基于数组的第 k 个数字来调整,使得比第 k 个数字小的所有数字都位于数组的左边,比第 k 个数字大的所有数字都位于数组的右边。这样调整之后,位于数组中左边的 k 个数字就是最小的 k 个数字(这 k 个数字不一定是排序的)。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54publicclassLeastK{

publicstaticvoidgetLeastNumbers(int[]input,int[]output){

if(input==null||output==null||output.length<=0||input.length

thrownewIllegalArgumentException("Invalid args");

}

intstart=0;

intend=input.length-1;

intindex=partition(input,start,end);// 切分后左子数组的长度

inttarget=output.length-1;// K-1

// 若切分后左子数组长度不等于K

while(index!=target){// 若切分后左子数组长度小于K,那么继续切分右子数组,否则继续切分左子数组

if(index

start=index+1;

}else{

end=index-1;

}

index=partition(input,start,end);

}

System.arraycopy(input,0,output,0,output.length);

}

privatestaticintpartition(intarr[],intleft,intright){

inti=left;

intj=right+1;

intpivot=arr[left];

while(true){// 找到左边大于pivot的数据,或者走到了最右边仍然没有找到比pivot大的数据

while(i pivot

if (i == right) {

break;

}

}// 找到右边小于pivot的数据,或者走到了最左边仍然没有找到比pivot小的数据

while(j>left&& arr[--j] > pivot) { // 求最大的k个数时,arr[--j] < pivot

if (j == left) {

break;

}

}// 左指针和右指针重叠或相遇,结束循环

if(i>=j){

break;

}// 交换左边大的和右边小的数据

swap(arr,i,j);

}// 此时的 a[j] <= pivot,交换之

swap(arr,left,j);

returnj;

}

privatestaticvoidswap(int[]arr,inti,intj){

inttmp=arr[i];

arr[i]=arr[j];

arr[j]=tmp;

}

}

采用上面的思路是有限制的,比如需要修改输入的数组,因为函数 Partition 会调整数组中的顺序,当然了,这个问题完全可以通过事先拷贝一份新数组来解决。值得说明的是,这种思路是不适合处理海量数据的。若是遇到海量数据求最小的 k 个数的问题,可以使用下面的解法。

解法二:最小堆法

利用最小堆的思想,先读取前k个元素,建立一个最小堆。然后将剩余的所有元素依次与堆顶元素进行比较,如果大于堆顶元素,则堆顶弹出,否则,压入下一个数组元素继续比较,只要维护大小为k的堆就可以了,此方法适合处理海量数据,时间复杂度为O(nlogk)。java的PriorityQueue可以实现最小堆。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38importjava.util.Arrays;

importjava.util.PriorityQueue;

importjava.util.Random;

publicclassTopK{

publicstaticvoidmain(String[]args){

intnumber=100000000;// 一亿个数

intmaxnum=1000000000;// 随机数最大值

inttopnum=100;// 取最大的多少个

int[]nums=newint[100000000];

Randomrandom=newRandom();

for(inti=0;i

ints=Math.abs(random.nextInt(maxnum));

nums[i]=s;

}

Integer[]res=TopK.getLargestNumbers(nums,topnum);

System.out.println(Arrays.toString(res));

}

publicstaticInteger[]getLargestNumbers(int[]nums,intk){

PriorityQueueminQueue=newPriorityQueue<>(k);// 默认自然排序

for(intnum:nums){

if(minQueue.size()minQueue.peek()){// peek():返回队列头部的值,也就是队列最小值

// 插入元素

minQueue.offer(num);

}

if(minQueue.size()>k){// 删除队列头部

minQueue.poll();

}

}

returnminQueue.toArray(newInteger[0]);

}

}

也可以将上述代码改为手动实现最小堆的方式,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105importjava.util.Arrays;

importjava.util.Date;

importjava.util.Random;

publicclassTop100{

publicstaticvoidmain(String[]args){

find();

}

publicstaticvoidfind(){//

intnumber=100000000;// 一亿个数

intmaxnum=1000000000;// 随机数最大值

inti=0;

inttopnum=100;// 取最大的多少个

DatestartTime=newDate();

Randomrandom=newRandom();

int[]top=newint[topnum];

for(i=0;i

top[i]=Math.abs(random.nextInt(maxnum));//设置为随机数

//            top[i] = getNum(i);

}

buildHeap(top,0,top.length);// 构建最小堆, top[0]为最小元素

for(i=topnum;i

intcurrentNumber2=Math.abs(random.nextInt(maxnum));//设置为随机数

//            int currentNumber2 = getNum(i);

// 大于 top[0]则交换currentNumber2  重构最小堆

if(top[0]

top[0]=currentNumber2;

shift(top,0,top.length,0);// 构建最小堆 top[0]为最小元素

}

}

System.out.println(Arrays.toString(top));

sort(top);

System.out.println(Arrays.toString(top));

DateendTime=newDate();

System.out.println("用了"+(endTime.getTime()-startTime.getTime())+"毫秒");

}

publicstaticintgetNum(inti){

returni;

}

//构造排序数组

publicstaticvoidbuildHeap(int[]array,intfrom,intlen){

intpos=(len-1)/2;

for(inti=pos;i>=0;i--){

shift(array,from,len,i);

}

}

/**

* @param array top数组

* @param from 开始

* @param len 数组长度

* @param pos 当前节点index

* */

publicstaticvoidshift(int[]array,intfrom,intlen,intpos){

// 保存该节点的值

inttmp=array[from+pos];

intindex=pos*2+1;// 得到当前pos节点的左节点

while(index

{

if(index+1

&& array[from + index] > array[from + index + 1])// 如果存在右节点

{

// 如果右边节点比左边节点小,就和右边的比较

index += 1;

}

if(tmp>array[from+index]){

array[from+pos]=array[from+index];

pos=index;

index=pos*2+1;

}else{

break;

}

}

// 最终全部置换完毕后 ,把临时变量赋给最后的节点

array[from+pos]=tmp;

}

publicstaticvoidsort(int[]array){

for(inti=0;i

//当前值当作最小值

intmin=array[i];

for(intj=i+1;j

if(min>array[j]){

//如果后面有比min值还小的就交换

min=array[j];

array[j]=array[i];

array[i]=min;

}

}

}

}

}

若是遇到此类求海量数据中最小的 k 个数的问题,只需改用最大堆即可。构建最大堆,需要重写compare方法。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50importjava.util.Arrays;

importjava.util.Comparator;

importjava.util.PriorityQueue;

importjava.util.Random;

publicclassTopKK{

publicstaticvoidmain(String[]args){

intnumber=100000000;// 一亿个数

intmaxnum=1000000000;// 随机数最大值

inttopnum=100;// 取最大的多少个

int[]nums=newint[100000000];

Randomrandom=newRandom();

for(inti=0;i

ints=Math.abs(random.nextInt(maxnum));

nums[i]=s;

}

Integer[]res=TopKK.getLeastNumbers(nums,topnum);

System.out.println(Arrays.toString(res));

}

publicstaticInteger[]getLeastNumbers(int[]nums,intk){// 默认自然排序,需手动转为降序

PriorityQueuemaxQueue=newPriorityQueue<>(k,newComparator(){

@Override

publicintcompare(Integero1,Integero2){

//if (o1 > o2) {

//    return -1;

//} else if (o1 < o2) {

//    return 1;

//}

//return 0;

returno2.compareTo(o1);

}

});

for(intnum:nums){

if(maxQueue.size()

// 插入元素

maxQueue.offer(num);

}

if(maxQueue.size()>k){// 删除队列头部

maxQueue.poll();

}

}

returnmaxQueue.toArray(newInteger[0]);

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值