本文最后更新于:June 20, 2022 pm
大根堆 大根堆就是根节点是整颗树的最大值(根节点大于等于左右子树的最大值),对于他的任意子树,根节点也是最大值。小根堆则相反
要求:
根节点的关键字既大于或等于左子树的关键字值,又大于或等于右子树的关键字值
为完全二叉树 ,所以可以用数组来存储。i 结点的父结点下标就为 (i – 1) / 2
它的左右子结点下标分别为2 * i + 1
和2 * i + 2
创建大根堆 比如一棵树有 N 个元素,存放在数组里分别对应0 ~ N-1
,假设数组中从0到 i - 1 位置的元素是一个大根堆,然后把第 i 个位置的元素插入大根堆里,构造一个新的大根堆,就需要从第i个位置的元素开始,依次看它的父节点是否小于它,如果小于就进行交换,直到它的父节点不小于它,或者到了该大根堆的最顶端的根节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public static void heapInsert (int [] arr, int index) { while (arr[index] > arr[(index - 1 ) / 2 ]){ swap(arr, index, (index - 1 ) / 2 ); index = (index - 1 ) / 2 ; } }private static void swap (int [] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }public static void main (String[] args) { int [] arr = {9 ,43 ,-54 ,4 ,-13 ,10 ,36 }; for (int i = 0 ; i < arr.length; i++) { heapInsert(arr, i); } System.out.println(Arrays.toString(arr)); }
调整大根堆 设数组中对应的大根堆的长度为 heapSize 。当数组中下标为 index 的元素的值发生了变化,就要对这个堆进行调整,保证它还是大根堆。
具体过程是:将 index 对应的元素和它的左右子节点的值进行比较,如果比它小的话,就把 index 对应的元素和它的左右子节点中最大的值进行交换,交换后对他的子节点也执行这个过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static void heapAdjust (int [] arr, int index, int heapSize) { int left = index * 2 + 1 ; int right = left + 1 ; while (left < heapSize){ int max = right < heapSize && arr[right] > arr[left] ? right : left; max = arr[max] > arr[index] ? max : index; if (max == index){ break ; } swap(arr, max, index); index = max; left = index * 2 + 1 ; } }
堆排序 利用创建大根堆 和调整大根堆 来进行排序。
创建完大根堆以后,每一次都把堆顶 的元素和堆的最后一个 元素进行交换,并且把堆的长度heapSize 减小1,然后对新的堆进行调整,重新调整为大根堆,然后再取堆顶的元素和堆的最后一个节点进行交换,大根堆的长度heapSize 减小1,然后再调整,重复这个过程,直到堆里面剩余的元素个数为1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static void heapSort (int [] arr) { if (arr == null || arr.length < 2 ){ return ; } for (int i = 0 ; i < arr.length; i++) { heapInsert(arr, i); } int size = arr.length; swap(arr, 0 , --size); while (size > 0 ){ heapAdjust(arr, 0 , size); swap(arr, 0 , --size); } }
程序执行结果:
应用 leetcode剑指offer第40题
参考答案
用一个大根堆实时维护数组的前 k 小值。首先将前 k 个数插入大根堆中,随后从第 k+1 个数开始遍历,如果当前遍历到的数比大根堆的堆顶的数要小,就把堆顶的数弹出,再插入当前遍历到的数。最后将大根堆里的数存入数组返回即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public static int [] getLeastNumbers2(int [] arr, int k){ int [] vec = new int [k]; if (k == 0 ) { return vec; } PriorityQueue<Integer> queue = new PriorityQueue<>((num1, num2) -> num2 - num1); for (int i = 0 ; i < k; ++i) { queue.offer(arr[i]); } for (int i = k; i < arr.length; ++i) { if (queue.peek() > arr[i]) { queue.poll(); queue.offer(arr[i]); } } for (int i = 0 ; i < k; ++i) { vec[i] = queue.poll(); } return vec; }
PriorityQueue优先级队列 用法:
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>(){ @Override public int compare (Integer num1, Integer num2) { return num2 - num1; } }); PriorityQueue<Integer> queue = new PriorityQueue<>((num1, num2) -> num2 - num1);
Compare含义:
compare(Integer num1, Integer num2)
具体场景 TopK 问题:
该题就是第二种
参考
百度百科
图解大根堆的堆排序
如何构建一个大根堆
堆排序
数据结构—堆、大根堆、小根堆
PriorityQueue优先级队列用法