在原来基础上增长了算法E。html
这就是相似求Top(K)问题,什么意思呢?怎么在无序数组中找到第几(K)大元素?咱们这里不考虑海量数据,能装入内存。java
将数组中的元素升序排序,找到数组下标k-1的元素便可。这是你们最容易想到的方法,若是使用简单排序算法,时间复杂度为O(n^2)。算法
其实求第K大问题,也能够求反,即求第N-k+1小问题。这是等价的。因此当K=N/2时,是最难的地方,但也颇有趣,这时候的K对应的值就是中位数。数组
算法思想:将数据读入一个数组,对数组进行buildHeap(咱们这里构建大顶堆),以后对堆进行K次deleteMax操做,第K次的结果就是咱们须要的值。(由于是大顶堆,因此数据从大到小排了序,堆排序之后会详细说)。app
如今咱们来解决上节遗留的问题,为何buildHeap是线性的?不熟悉堆的能够看一下 图解优先队列(堆)。咱们先来看看代码实现。优化
public PriorityQueue(T[] items) {
//当前堆中的元素个数
currentSize = items.length;
//可自行实现声明
array = (T[]) new Comparable[currentSize +1];
int i = 1;
for (T item : items){
array[i++] = item;
}
buildHeap();
}
private void buildHeap() {
for (int i = currentSize / 2; i > 0; i--){
//堆的下滤方法,可参考上面的连接
percolateDown(i);
}
}
图中初始化的是一颗无序树,通过7次percolateDown后,获得一个大顶堆。从图中能够看到,共有9条虚线,每一条对应于2次比较,总共18次比较。为了肯定buildHeap的时间界,咱们须要统计虚线的条数,这能够经过计算堆中全部节点的高度和获得,它是虚线的最大条数。该和是O(N)。ui
定理:包含2h+1-1个节点、高为h的理想二叉树(满二叉树)的节点的高度的和是2h+1-1-(h+1)。spa
什么叫满二叉树?满二叉树是彻底填满的二叉树,最后一层都是填满的,如图中所示。彻底二叉树,是除最后一层之外都是填满的,最后一层外也必须从左到右依次填入,就是上一篇中说的堆的结构。满二叉树必定是彻底二叉树,彻底二叉树不必定是满二叉树。code
证实定理:orm
容易看出,满二叉树中,高度为h上,有1个节点;高度h-1上2个节点,高度h-2上有2^2个节点及通常在高度h-i上的2i个节点组成。
方程两边乘以2获得:
两式相减获得:
因此定理得证。由于堆由彻底二叉树构成,因此堆的节点数在2h和2h+1之间,因此意味着这个和是O(N)。因此buildHeap是线性的。因此算法C的时间复杂度是:初始化数组为O(N),buildHeap为O(N),K次deleeMax须要O(klogN),因此总的时间复杂度是:O(N+N+klogN)=O(N+klogN),若是K为N/2时,运行时间是O(NlogN)。
参考快速排序及其优化,使用快速排序的思想,进行一点点改动。
public class QuickSelect {
/**
* 截止范围
*/
private static final int CUTOFF = 5;
public static void main(String[] args) {
Integer[] a = {8, 1, 4, 9, 6, 3, 5, 2, 7, 0, 12, 11, 15, 14, 13, 20, 18, 19, 17, 16};
int k = 5;
quickSelect(a, a.length - k + 1);
System.out.println("第" + k + "大元素是:" + a[a.length - k]);
}
public static <T extends Comparable<? super T>> void quickSelect(T[] a, int k) {
quickSelect(a, 0, a.length - 1, k);
}
private static <T extends Comparable<? super T>> void quickSelect(T[] a, int left, int right, int k) {
if (left + CUTOFF <= right) {
//三数中值分割法获取枢纽元
T pivot = median3(a, left, right);
//开始分割序列
int i = left, j = right - 1;
for (; ; ) {
while (a[++i].compareTo(pivot) < 0) {
}
while (a[--j].compareTo(pivot) > 0) {
}
if (i < j) {
swapReferences(a, i, j);
} else {
break;
}
}
//将枢纽元与位置i的元素交换位置
swapReferences(a, i, right - 1);
if (k <= i) {
quickSelect(a, left, i - 1, k);
} else if (k > i + 1) {
quickSelect(a, i + 1, right, k);
}
} else {
insertionSort(a, left, right);
}
}
private static <T extends Comparable<? super T>> T median3(T[] a, int left, int right) {
int center = (left + right) / 2;
if (a[center].compareTo(a[left]) < 0) {
swapReferences(a, left, center);
}
if (a[right].compareTo(a[left]) < 0) {
swapReferences(a, left, right);
}
if (a[right].compareTo(a[center]) < 0) {
swapReferences(a, center, right);
}
// 将枢纽元放置到right-1位置
swapReferences(a, center, right - 1);
return a[right - 1];
}
public static <T> void swapReferences(T[] a, int index1, int index2) {
T tmp = a[index1];
a[index1] = a[index2];
a[index2] = tmp;
}
private static <T extends Comparable<? super T>> void insertionSort(T[] a, int left, int right) {
for (int p = left + 1; p <= right; p++) {
T tmp = a[p];
int j;
for (j = p; j > left && tmp.compareTo(a[j - 1]) < 0; j--) {
a[j] = a[j - 1];
}
a[j] = tmp;
}
}
}
//输出结果
//第5大元素是:16
由于我这里是升序排序,因此求第K大,与求第N-k+1是同样的。
本篇详述了 求top(K)问题的几种解法,前两种十分平凡普通,后两种比较优一点,暂时给出求解中位数须要O(NlogN)时间。后面介绍使用快速选择方法,每次只用递归一个子序列,能够达到平均O(N)时间复杂度。