title: 堆的相关以及topK问题的JAVA实现
date: 2020-03-09 21:03:42
tags:
堆的相关
什么是堆:
- 完全二叉树
- 每个节点的值大于等于或者小于等于其左右子节点的值,即大顶堆和小顶堆。
完全二叉树使用数组来存储,不需要使用左右指针,仅通过数组下标就可以访问左右子节点,从0开始计算,下标为i
的节点的左节点为2*i+1
,右节点为2*i+2
。
相关操作
以大顶堆为例
将某个数组元素堆化,从这个节点出发,直到节点大于其左右子节点的值:
/**
* 堆化
*
* @param arr 要堆化的数组
* @param n 最后堆元素下标
* @param i 要堆化的元素下标
*/
private static void heapify(int[] arr, int n, int i) {
while (true) {
// 最大值位置
int maxPos = i;
// 与左子节点(i * 2 + 1)比较,获取最大值位置
if (i * 2 + 1 <= n && arr[i] < arr[i * 2 + 1]) {
maxPos = i * 2 + 1;
}
// 最大值与右子节点(i * 2 + 2)比较,获取最大值位置
if (i * 2 + 2 <= n && arr[maxPos] < arr[i * 2 + 2]) {
maxPos = i * 2 + 2;
}
// 最大值是当前位置结束循环
if (maxPos == i) {
break;
}
// 与子节点交换位置
swap(arr, i, maxPos);
// 以交换后子节点位置接着往下查找
i = maxPos;
}
}
建堆:
/**
* 建堆
*
* @param arr
*/
private void buildHeap(int[] arr) {
// (arr.length - 1) / 2 为最后一个叶子节点的父节点
// 也就是最后一个非叶子节点,依次堆化直到根节点
for (int i = (arr.length - 1) / 2; i >= 0; i--) {
heapify(arr, arr.length - 1, i);
}
}
堆排序:
/**
* 排序
* <p>
* 堆元素是从数组下标0开始
*
* @param arr
*/
public void sort(int[] arr) {
if (arr.length <= 1) {
return;
}
// 1、建堆
buildHeap(arr);
// 2、排序
int k = arr.length - 1;
while (k > 0) {
// 将堆顶元素(最大)与最后一个元素交换位置
swap(arr, 0, k);
// 将剩下元素重新堆化,堆顶元素变成最大元素
heapify(arr, --k, 0);
}
}
删除堆顶元素,将最后一个叶子节点,置于堆顶,然后将其堆化:
/**
* @param arr
* @param n 最后堆元素下标
*/
public void delTop(int[] arr , int n){
if(n < 0) return;
arr[0] = arr[n];
heapify(arr , --n , 0);
}
牛客上有道题,求最小的K个数,我们就可以使用堆来实现。
题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是>1,2,3,4,。
可以先把数组的前K个数拿出来,构建一个大顶堆,然后从第K+1个数开始循环,如果这个数小于堆顶元素,即把这个数置于堆顶,然后堆化。
一次堆化的时间复杂度为O(logK),遍历数组O(n),假设每个元素都要入堆,则时间复杂度为O(nlongK)
/**
* @param k 求top K元素,
* @param a
* @return
*/
public int[] topK(int k , int[] a){
int count = 0;
int[] re = new int[k];
for (int i = 0; i < k; i++) {
re[i] = a[i];
}
//堆化K个元素
for (int i = (k - 1) / 2; i >= 0; i--) {
heapify(re, k-1, i);
}
for (int j = k; j < a.length; j++) {
if(re[0] > a[j]){
re[0] = a[j];
heapify(re , re.length - 1 , 0);
}
}
return re;
}