如果堆中每个节点的值都大于等于子树中每个节点的值,这种堆叫做大根堆
如果堆中每个节点的值都小于等于子树中每个节点的值,这种堆叫做小根堆
由于堆是完全二叉树,所以堆适合用数组来进行存储
将新数据插入到数组末尾,然后执行自下而上的堆化操作
拿大顶堆举例,如果插入的数据让堆不满足大顶堆的要求就需要自下而上进行堆化
不断和父节点进行比较,如果比父节点的值大,就和父节点交换
满足大顶堆条件或者到达根节点时,停止交换
public class Heap {private int[] a; // 数组,从下标1开始存数据private int n; // 堆可以存储的最大数据个数private int count; // 堆中已经存储的数据的个数public Heap(int capacity) {a = new int[capacity + 1];n = capacity;count = 0;}public int insert(int data) {if (count >= n) return; // 堆满了++count;a[count] = data;int i = count;while (i / 2 > 0 && a[i] > a[i / 2]) { // 自下往上堆化swap(a, i, i / 2);i = i / 2;}}private void swap(int[] a, int i, int j) {int tmp = a[i];a[i] = a[j];a[j] = tmp;}
}
大顶堆的堆顶元素是堆中最大的元素
小顶堆的堆顶元素是堆中最小的元素
public int top() {if (count == 0) return Integer.MIN_VALUE;return a[1];
}
将最后一个节点放到堆顶,然后利用自上而下的堆化方式让堆满足条件
如果不交换节点,直接进行自上而下的堆化方式,容易产生空洞,从而不满足完全二叉树的条件
public void removeTop() {if (count == 0) return; // 堆中没有数据a[1] = a[count];--count;heapify(a, count, 1);
}private void heapify(int[] a, int n, int i) { // 自上往下堆化while (true) {int maxPos = i;if (i * 2 <= n && a[i] < a[i * 2]) maxPos = i * 2;if (i * 2 + 1 <= n && a[maxPos] < a[i * 2 + 1]) maxPos = i * 2 + 1;if (maxPos == i) break;swap(a, i, maxPos);i = maxPos;}
}
如果更新之后的值变小了,就进行自上往下的堆化
如果更新之后的值变大了,就进行自下往上的堆化
堆排序是原地排序算法但不是稳定排序算法,时间复杂度:O(nlogn),空间复杂度:O(1)
堆排序包含两个大的步骤:
第一种实现:从前往后处理每个元素,对每个元素执行自下往上的堆化
第二种实现:从后往前处理每个元素,对每个元素执行自上往下的堆化
private void buildHeap(int[] a, int n) {for (int i = n / 2; i >= 1; --i) {heapify(a, n, i);}
}private void heapify(int[] a, int n, int i) {while (true) {int maxPos = i;if (i * 2 <= n && a[i] < a[i * 2]) maxPos = i * 2;if (i * 2 <= n && a[maxPos] < a[i * 2 + 1]) maxPos = i * 2 + 1;if (maxPos == i) break;swap(a, i, maxPos);i = maxPos;}
}
// 方法名称参照上面的代码
public void sort(int[] a, int n) {buildHeap(a, n);int k = n;while (k > 1) {swap(a, 1, k);--k;heapify(a, k, 1);}
}
链接:https://leetcode.cn/problems/merge-k-sorted-lists
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i] 按 升序 排列
lists[i].length 的总和不超过 10^4
由于数组中封装的是一个一个的链表,所以可以先将所有链表的头节点的数据传到小顶堆中,每次传出根节点之后,再传入根节点对应的链表的下一个节点的数据
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode mergeKLists(ListNode[] lists) {if (lists == null || lists.length == 0) return null;int k = lists.length;PriorityQueue minQ = new PriorityQueue<>(new Comparator() {public int compare(ListNode p1, ListNode p2) {return p1.val - p2.val;}});for (int i = 0; i < k; ++i) {if (lists[i] != null) {minQ.offer(lists[i]);}}ListNode head = new ListNode();ListNode tail = head;while(!minQ.isEmpty()) {ListNode curNode = minQ.poll();tail.next = curNode;tail = tail.next;if (curNode.next != null) {minQ.offer(curNode.next);}}return head.next;}
}
维护一个大小为k的小顶堆,有数据被添加时
如果堆中的数据个数小于k,直接将新数据插入小顶堆
如果堆中的数据等于k,就拿新添加的数据与堆顶元素比较
如果新添加的数据大于堆顶元素,就将堆顶元素删除,将新添加的数据插入堆中
如果新添加的数据小于等于堆顶元素,就不对堆进行任何处理
链接:https://leetcode.cn/problems/top-k-frequent-elements
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
class Solution {private class QElement {int val;int count;public QElement(int val, int count) {this.val = val;this.count = count;}}public int[] topKFrequent(int[] nums, int k) {Map counts = new HashMap<>();for (int num : nums) {counts.put(num, counts.getOrDefault(num, 0) + 1);}PriorityQueue queue = new PriorityQueue<>(new Comparator() {public int compare(QElement e1, QElement e2) {return e1.count - e2.count;}});for (Map.Entry entry : counts.entrySet()) {int num = entry.getKey();int count = entry.getValue();if (queue.size() < k) {queue.offer(new QElement(num, count));} else {if (queue.peek().count < count) {queue.poll();queue.offer(new QElement(num, count));}}}int[] result = new int[k];for (int i = 0; i < k; ++i) {result[i] = queue.poll().val;}return result;}
}
求中位数要维护具有以下两个特征的堆
那么大顶堆中堆顶的元素就是中位数
如果新数据小于等于大顶堆的堆顶元素,就将新数据插入到大顶堆,否则,插入到小顶堆
此时有可能出现两个堆中的数据个数不符合前面的规定。可以通过一个堆中不停的将堆顶元素移动到另一个堆,让两个堆中的数据个数满足前面的约定
求百分位数和中位数类似,同样需要维护两个堆
假设当前数据个数为n,大顶堆中保存前99%*n个数据,小顶堆中保存后1% * n个数据。
大顶堆堆顶元素就是要找的99百分位数据
其余基本和中位数一样
链接:https://leetcode.cn/problems/find-median-from-data-stream
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
例如 arr = [2,3,4] 的中位数是 3 。
例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5 。
实现 MedianFinder 类:
MedianFinder() 初始化 MedianFinder 对象。
void addNum(int num) 将数据流中的整数 num 添加到数据结构中。
double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。
示例 1:
输入
[“MedianFinder”, “addNum”, “addNum”, “findMedian”, “addNum”, “findMedian”]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]
解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0
提示:
-105 <= num <= 105
在调用 findMedian 之前,数据结构中至少有一个元素
最多 5 * 104 次调用 addNum 和 findMedian
class MedianFinder {private PriorityQueue minQueue = new PriorityQueue(new Comparator() {public int compare(Integer o1, Integer o2) {return o1 - o2;}});private PriorityQueue maxQueue = new PriorityQueue(new Comparator() {public int compare(Integer o1, Integer o2) {return o2 - o1;}});public MedianFinder() {}public void addNum(int num) {if (maxQueue.isEmpty() || num <= maxQueue.peek()) {maxQueue.add(num);} else {minQueue.add(num);}while (maxQueue.size() < minQueue.size()) {Integer e = minQueue.poll();maxQueue.add(e);}while (minQueue.size() < maxQueue.size() - 1) {Integer e = maxQueue.poll();minQueue.add(e);}}public double findMedian() {if (maxQueue.size() > minQueue.size()) {return maxQueue.peek();} else {return (maxQueue.peek() + minQueue.peek()) / 2f;}}
}/*** Your MedianFinder object will be instantiated and called as such:* MedianFinder obj = new MedianFinder();* obj.addNum(num);* double param_2 = obj.findMedian();*/