如果堆中每个节点的值都大于等于子树中每个节点的值,这种堆叫做大根堆
如果堆中每个节点的值都小于等于子树中每个节点的值,这种堆叫做小根堆
由于堆是完全二叉树,所以堆适合用数组来进行存储
将新数据插入到数组末尾,然后执行自下而上的堆化操作
拿大顶堆举例,如果插入的数据让堆不满足大顶堆的要求就需要自下而上进行堆化
不断和父节点进行比较,如果比父节点的值大,就和父节点交换
满足大顶堆条件或者到达根节点时,停止交换
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<ListNode> minQ = new PriorityQueue<>(new Comparator<ListNode>() {
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<Integer, Integer> counts = new HashMap<>();
for (int num : nums) {
counts.put(num, counts.getOrDefault(num, 0) + 1);
}
PriorityQueue<QElement> queue = new PriorityQueue<>(new Comparator<QElement>() {
public int compare(QElement e1, QElement e2) {
return e1.count - e2.count;
}
});
for (Map.Entry<Integer, Integer> 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<Integer> minQueue = new PriorityQueue(new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
private PriorityQueue<Integer> maxQueue = new PriorityQueue(new Comparator<Integer>() {
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();
*/