问题描述
就是给出一个随机序列,序列元素可比较,查出第K大元素或者第K小元素。
这是一个经典的算法题,之前也写过,这里总结一下思路。
思路一:先快排再直接取元素
对于一个随机序列,快排的性能应该是最好的啦(基础排序算法),稍加优化性能更佳。
我们先对序列用 数组/顺序 表存储起来,再快排(如果懒得写,Java可用java.util.Arrays.sort(),C++可用STL的sort())。
排完以后就利用其直接访问的特点取第K大/小元素即可。
该策略时间复杂度O(NlogN)。
思路二:维护一个大小为K的乱序数组进行替换
我们可以先取下来随机序列前K个元素,放在一个乱序的数组中,每次都换掉其中最小/大的元素(前提是比这个元素大/小)。
这样就不需要完整地排序,时间复杂度O(N*K)。
当K>logN的时候就是血亏,还不如直接排序呢。
思路三:维护一个堆最后直接取堆顶元素
我们上面的算法时间复杂度之所以需要×K,是因为乱序,每次要找最小/大元素,那我们不如维护一个大小为K的堆。求第K大就建立小根堆,最后直接取堆顶;求第K小就建立大根堆,最后也是直接取堆顶。
具体操作就是先拿前K个元素建立二叉小根堆/大根堆,需要时间O(K),然后每次调整也就是O(logK),次数是(N-K),所以是O(K+logK(N-K)),如果说K<<N,就近似于O(NlogK),这就能实现很大程度上的优化了。
思路四:分治法
这个思路很秀,简单说一下。
怎么分治呢?大家还记得快排吧,其实很相似,我们利用了与其“划分”很类似的做法。
以第K小元素为例:
取一个中间元素放到最左边,比其小的换到左边,比其大的不动,完成划分,不必排序。
划分完成就看看选取的中间值的id与K的关系,最后当K与id吻合时,左边的元素就是比K小的元素,划分中值就是第K小元素。
该算法的时间复杂度甚至能达到O(N),是不是很玄学?红红火火恍恍惚惚哈哈哈哈哈……
第K小元素-思路一-Java编程实现
第K大元素-思路三-Java编程实现
import java.util.Scanner;
public class Main {
/**
* 寻找第k大的元素
* @param array 待调整的堆
* @param k 第几大
*/
public static int findNumberK(int[] array, int k){
//用前k个元素构建小顶堆
buildHeap(array, k);
//继续遍历数组,和堆顶比较
for (int i=k; i<array.length;i++){
if (array[i] > array[0]){
array[0] = array[i];
downAdjust(array, 0, k);
}
}
//返回堆顶元素
return array[0];
}
/**
* 构建堆
* @param array 待调整的堆
* @param length 堆的有效大小
*/
private static void buildHeap(int[] array, int length) {
//从最后一个非叶子节点开始,依次下沉调整
for (int i = (length- 2)/ 2; i >= 0; i--) {
downAdjust(array, i, length);
}
}
/**
* 下沉调整
* @param array 待调整的堆
* @param index 要下沉的节点
* @param length 堆的有效大小
*/
private static void downAdjust(int[] array, int index, int length) {
//temp保存父节点值,用于最后的赋值
int temp = array[index];
int childIndex = 2 * index + 1;
while (childIndex < length) {
//如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]) {
childIndex++;
}
//如果父节点小于任何一个孩子的值,直接跳出
if (temp <= array[childIndex])
break;
//无需真正交换,单向赋值即可
array[index] = array[childIndex];
index = childIndex;
childIndex = 2 * childIndex + 1;
}
array[index] = temp;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt(), k = scanner.nextInt();
int[] array = new int[num];
for (int i = 0; i < num; i++) {
array[i] = scanner.nextInt();
}
scanner.close();
System.out.println(findNumberK(array, k));
}
}