方法一:常规方法,先完全排序

此种方法就不多做解释了,就是使用快排,归并,堆排序等方法先将数组完全排序,然后再取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