方法一:常规方法,先完全排序
此种方法就不多做解释了,就是使用快排,归并,堆排序等方法先将数组完全排序,然后再取topK,时间复杂度为O(NlogN)。而且这种方法不适用于大数据量,小内存。
方法二:快排思想,部分排序
此种方法时借鉴快排的思想,在partition过程中,将数组分为了三个部分,左边是小于指定的数,中间是等于指定的数,右边是大于指定的数,这样,我们只需要让数组部分有序就可以拿到topK的值,不用完全有序,这种方法最差的情况复杂度为O(NlogN),下面的代码我加了一些判断之类,写的比较冗余,核心思想其实是partition,findK两个函数。这里的partition是一般快排partition的改进版,详细解释请看我的这篇文章归并排序、快速排序、堆排序---Java实现(带注释)。这种方法和第一种方法一样,还是不适用于大数据量小内存,因为这两种方法需要将数组一次性加载到内存中。
/**
* @param arr
* @param k
* @return :返回topK的list
*/
public static List<Integer> findTopK(int[] arr ,int k ) {
List<Integer> list = new ArrayList<>();
if (arr == null || arr.length < 1) {
return list;
}
if (k > arr.length) {
for (int num : arr) {
list.add(num);
}
} else {
// 代用findK方法让数组部分排序,直接获取arr.length - k到arr.length的所有元素
Top_K.findK(arr, 0, arr.length - 1, k);
for (int i = arr.length - k; i <arr.length; i++) {
list.add(arr[i]);
}
}
return list;
}
/**
* 找到第k大元素,并且此时第k大元素所在下标到arr.length都是大于第k大元素的元素
* @param array
* @param left
* @param right
* @param k:所要找的最大值k
* @return :返回找到的第k大元素
*/
public static int findK(int[] array, int left, int right, int k) {
int[] p = partition(array, left, right);
int index = array.length - k ;
if (p[1] == index) {
return array[index];
} else if (p[1] > index) {
return findK(array, left, p[1] - 1, k);
} else if (p[1] < index) {
return findK(array, p[1] + 1, right, k);
}
return 0;
}
/**
* 将数组以arr[r]分割为三部分,小于arr[r],等于arr[r],大于arr[i]
* @param arr:需要partition的数组
* @param l:partition数组的左边界
* @param r:partition数组的右边界
* @return :返回分割元素所在的左右下标
*/
public static int[] partition(int[] arr, int l, int r) {
int less = l-1;
int more = r;
while ( l < more ) {
if (arr[l] < arr[r]) {
swap(arr, ++less, l++);
} else if (arr[l] > arr[r]) {
swap(arr, l, --more);
} else {
l++;
}
}
swap(arr,more,r);
return new int[] { less + 1, more };
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
第三种方法:堆排序,内存每次只需要加载K个元素,适用于大数据量小内存
堆排序完全是解决大数据量小内存的一种很好的方法,因为它每次只需要加载K个元素到主内存中,然后接着每次来一个元素,就通过和堆的最小元素进行比较,如果比最小元素大,那么移除最小元素,将此元素进加入到堆,堆进行动态调整。最后堆中所保留的元素就是数组中最大的K个元素,时间复杂度为O(NlogK)。这里使用PriorityQueue优先,PriorityQueue可以指定大小以及比较器,默认为小顶堆,所以下面的实现代码中是不用重写比较器的。
/**
* @param input:需要排序的数组
* @param k:需要的top K
* @return :返回top K的list集合
*/
public static List<Integer> findTopK1(int[] input, int k) {
List<Integer> list = new ArrayList<>();
// 这里其实不用重写compare方法,因为默认的就是从小到大排序
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, (o1, o2) -> o1 - o2);
if (input == null || input.length < 1) {
return list;
}
for (int num : input) {
// 在不够k之前,直接添加
if (maxHeap.size() < k) {
maxHeap.offer(num);
}
// 小顶堆的堆顶元素<num,那么就将其替换,优先队列会自动进行调整小顶堆的顺序
else if (maxHeap.peek() < num) {
maxHeap.poll();
maxHeap.offer(num);
}
}
list.addAll(maxHeap.stream().collect(Collectors.toList()));
return list;
}
测试代码:
public class Top_K {
public static void main(String[] args) {
int[] arr = Top_K.reduceArr(100);
System.out.println("========测试arr========");
System.out.println(Arrays.toString(arr));
System.out.println("========堆排序topK========");
List<Integer> list = Top_K.findTopK(arr, 10);
// 排序是为了方便观察
Collections.sort(list);
System.out.println(list.toString());
System.out.println("========快排思想topK========");
List<Integer> list1 = Top_K.findTopK1(arr, 10);
// 排序是为了方便观察
Collections.sort(list1);
System.out.println(list1.toString());
}
/**
*
* @param num:要生成的数组长度
* @return :返回随机生成的数组
*/
public static int[] reduceArr(int num) {
int[] res = new int[num];
for (int i = 0; i < num; i++) {
res[i] = (int) (Math.random() * num);
}
return res;
}
}
完整代码:Hollake