-
Notifications
You must be signed in to change notification settings - Fork 13
/
TopK.java
145 lines (130 loc) · 4.92 KB
/
TopK.java
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package algorithm.note;
/**
* @author: mayuan
* @desc: 从n个数中,找出最大的k个数,这就是经典的TopK问题.
* (1.排序是最容易想到的方法,将n个数排序之后,取出最大的k个,即为所得。 O(n*lg(n)))
* (2.采用冒泡排序,每冒一个泡,找出最大值,冒k个泡,就得到TopK。 O(n*k))
* @date: 2018/09/24
*/
public class TopK {
public static void main(String[] args) {
int[] array = {9, 3, 1, 10, 5, 7, 6, 2, 8, 0, 4};
final int k = 4;
getTopK(array, k);
for (int i = 0; i < k; i++) {
System.out.print(array[i] + " ");
}
System.out.println("\n*********************");
int[] ans = getTopK2(array.clone(), k);
for (int n : ans) {
System.out.print(n + " ");
}
System.out.println("\n*********************");
}
/**
* 基于快速排序的思想(从大到小排序,前k个数即为所求)
* 时间复杂度: O(n)
* <p>
* 我们知道,分治函数会返回一个position,在position左边的数都比第position个数大,在position右边的数都比第position小。
* 我们不妨不断调用分治函数,直到它输出的position = K-1,此时position前面的K个数(0到K-1)就是要找的前K个数。
*
* @param array
* @param k
*/
public static void getTopK(int[] array, int k) {
if (null == array || 0 >= array.length) {
return;
}
int left = 0;
int right = array.length - 1;
int index = partition(array, left, right);
// 不断调整轴的位置,直到 index = k-1
while (index != k - 1) {
if (index < k - 1) {
left = index + 1;
index = partition(array, left, right);
}
if (index > k - 1) {
right = index - 1;
index = partition(array, left, right);
}
}
}
public static int partition(int[] array, int left, int right) {
int pivot = array[left];
while (left < right) {
while (left < right && array[right] <= pivot) {
--right;
}
array[left] = array[right];
while (left < right && array[left] >= pivot) {
++left;
}
array[right] = array[left];
}
array[left] = pivot;
return left;
}
/**
* 基于堆排序的思想(构建一个小顶堆)
* 时间复杂度: n*log(K)
* 先用前k个元素生成一个小顶堆,这个小顶堆用于存储,当前最大的k个元素。
* 接着,从第k+1个元素开始扫描,和堆顶(堆中最小的元素)比较,如果被扫描的元素大于堆顶,
* 则替换堆顶的元素,并调整堆,以保证堆内的k个元素,总是当前已经扫描到数据中较大的k个元素。
* 扫描完所有n-k个元素,最终堆中的k个元素,就是要求的TopK。
* <p>
* 好处:
* 1:不会占用太多的内存空间(事实上,一次只读入一个数,内存只要求能容纳前K个数即可)
* 2:由于(1),决定了它特别适合处理海量数据
*
* @param array
* @param k
* @return
*/
public static int[] getTopK2(int[] array, int k) {
int[] top = new int[k];
for (int i = 0; i < k; ++i) {
top[i] = array[i];
}
// 构建小顶堆
for (int i = (top.length - 1) / 2; i >= 0; --i) {
minHeapDown(top, i, top.length - 1);
}
//构建小顶堆后,依次比较剩余元素与堆顶元素的大小,比堆顶大的,说明它应该在堆中出现,则用它来替换掉堆顶元素,然后沉降。
for (int i = k; i < array.length; ++i) {
if (top[0] <= array[i]) {
setHeapTop(top, array[i]);
}
}
return top;
}
public static void setHeapTop(int[] array, int top) {
array[0] = top;
// 遍历之后,得到的数组是一个(最小)二叉堆
for (int i = (array.length - 1) / 2; i >= 0; --i) {
minHeapDown(array, i, array.length - 1);
}
}
public static void minHeapDown(int[] array, int start, int end) {
int current = start;
//左子节点
int left = current * 2 + 1;
for (; left <= end; current = left, left = left * 2 + 1) {
// left指向左右子节点中的最小节点
if (left < end && array[left] > array[left + 1]) {
++left;
}
// 当前的堆已经是小顶堆,无需进行调整
if (array[current] <= array[left]) {
break;
} else {
swap(array, current, left);
}
}
}
public static void swap(int[] array, int x, int y) {
int temp = array[x];
array[x] = array[y];
array[y] = temp;
}
}