堆的概念及其在排序和TopK问题中的应用
堆(heap)是一种特殊的数据结构,其形象化是一个特殊的完全二叉树(CBT),满足任意节点始终不大于(或不小于)左右子节点。若该二叉树的根的值为最小值,称为小顶堆,反之则称为大顶堆。
1 1 1
/ \ / \ / \
2 3 4 5 3 2
/ \ / \ / \ /
4 5 2 3 6 5 4
(1) (2) (3)
not a CBT a CBT but not heap a mintop heap
然而堆的另一个特殊之处在于,通常用数组去存储堆,而不是二叉树。上图(3)中的小顶堆可以用[1,3,2,6,5,4]来表示。将任意一个数组转化为堆数组的操作称为堆化。
堆化
大顶堆
public static void buildMaxHeap(int[] arr){
int length = arr.length;
for(int i=length/2-1; i>=0; i--){
adjustMaxHeap(arr, i, length);
}
}
public static void adjustMaxHeap(int[] arr, int i, int length){
int temp = arr[i];
for(int k=2*i+1; k<length; k = k*2+1){
if(k+1<length && arr[k]<arr[k+1]){
k++;
}
if(arr[k] > temp){
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;
}
小顶堆
public static void buildMinHeap(int[] arr){
int length = arr.length;
for(int i=length/2-1; i>=0; i--){
adjustMinHeap(arr, i, length);
}
}
public static void adjustMinHeap(int[] arr, int i, int length){
int temp = arr[i];
for(int k=2*i+1; k<length; k=k*2+1){
if(k+1<length && arr[k]>arr[k+1]){
k++;
}
if(arr[k] < temp){
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;
}
堆排序
堆可以应用于排序算法中,若想得到升序,则建立大顶堆,反之则建立小顶堆。本文以升序为例,首先将待排序数组建立成一个大顶堆,此时,整个数组中最大值为堆顶的值,即arr[0],然后将arr[0]与末尾元素进行交换。然后将剩余的前n-1个元素重新构造成一个大顶堆,再将arr[0]与第n-1个元素进行交换。如此反复,即可对数组进行排序。代码如下
public static void sort(int[] arr){
buildMaxHeap(arr);
for(int i=arr.length-1; i>0; i--){
swap(arr, 0,i);
adjustMaxHeap(arr, 0, i);
}
}
public static void swap(int[] arr, int a, int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
TopK问题
问题描述:有 N个数,求出其中的前K个最大(小)的数。
这类问题如果求前K个最大的数,需要用到小顶堆,求前K个最小的数,需要用到大顶堆。本文以取前K个最大的数为例,首先取N个数中前K个数,建立一个大小为K的小顶堆。堆顶为最小的数,然后对N个数中剩余的N-K个数进行遍历,若原数组中的元素小于堆顶的元素,则将堆顶进行替换并调整堆。该算法的复杂度为O(N*logK),代码如下
public static int[] topK(int[] arr,int k){
int[] ans = Arrays.copyOfRange(arr, 0, k);
buildMinHeap(ans);
for(int i=k; i<arr.length; i++){
if(ans[0]<arr[i]){
ans[0] = arr[i];
adjustHeap(arr, 0, k);
}
}
return ans;
}