数据结构之优先队列(堆)-java
什么是优先队列?
队列是先进先出的数据结构,优先队列的出队可以出最小值或者最大值。
简单提一下,表list、队列、栈都可以由数组实现。当长度变长超出数组长时,新建一个两倍长的数组。
优先的队列的基本情况
1.优先队列的主要操作 优先队列是元素的容器,每个元素有一个相关的键值;
insert(key, data)
:插入键值为key的数据到优先队列中,元素以其key进行排序;deleteMin/deleteMax
:删除并返回最小/最大键值的元素;getMinimum/getMaximum
:返回最小/最大剑指的元素,但不删除它;
2.优先队列的辅助操作
第k最小/第k最大
:返回优先队列中键值为第k个最小/最大的元素;大小(size)
:返回优先队列中的元素个数;堆排序(Heap Sort)
:基于键值的优先级将优先队列中的元素进行排序;
3.优先队列的辅助操作
- 数据压缩:赫夫曼编码算法;
- 最短路径算法:Dijkstra算法;
- 最小生成树算法:Prim算法;
- 事件驱动仿真:顾客排队算法;
- 选择问题:查找第k个最小元素;
优先的队列的实现–堆
优先队列可以由无序数组、无序链表、树等实现,使用堆是最优解。
什么是堆
堆是一颗具有特定性质的二叉树,堆的基本要求就是堆中所有结点的值必须大于或等于(或小于或等于)其孩子结点的值,这也称为堆的性质;堆还有另一个性质,就是当 h > 0 时,所有叶子结点都处于第 h 或 h - 1 层(其中 h 为树的高度,完全二叉树),也就是说,堆应该是一颗完全二叉树;
堆的实现
数组
用数组来表示堆不仅不会浪费空间还具有一定的优势:
- 每个结点的左孩子为下标i的2倍:
left child(i) = i * 2
;每个结点的右孩子为下标i的2倍加1:right child(i) = i * 2 + 1
- 每个结点的父亲结点为下标的二分之一:
parent(i) = i / 2
,注意这里是整数除,2和3除以2都为1;
public class MaxHeap<E extends Comparable<E>> {
private Array<E> data;
public MaxHeap(int capacity){ data = new Array<>(capacity); }
public MaxHeap(){ data = new Array<>(); }
// 返回堆中的元素个数
public int size(){ return data.getSize(); }
// 返回一个布尔值, 表示堆中是否为空
public boolean isEmpty(){ return data.isEmpty(); }
// 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
private int parent(int index){
if(index == 0)
throw new IllegalArgumentException("index-0 doesn't have parent.");
return (index - 1) / 2;
}
// 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
private int leftChild(int index){ return index * 2 + 1; }
// 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
private int rightChild(int index){ return index * 2 + 2; }
}
// 向堆中添加元素
public void add(E e){
data.addLast(e);
siftUp(data.getSize() - 1);
}
private void siftUp(int k){
while(k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0 ){
data.swap(k, parent(k));
k = parent(k);
}
}
优先队列的声明
/ 默认实现了一个最小堆。
Queue<Integer> priorityQueue = new PriorityQueue<>();
// 实现最大堆
Queue<Integer> priorityQueue = new PriorityQueue<>((a, b) -> b - a);
在PriorityQueue提供的构造方法中,可以使用自定义的排序方法,也可以使用元素自带的Comparable排序,PriorityQueue要求在默认排序的时候,需要元素对象拥有Comparable功能。
private static Comparator<ListNode> comp =new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
// TODO Auto-generated method stub
return o1.val-o2.val;
}
};
public static ListNode mergeKLists(List<ListNode> lists){
PriorityQueue<ListNode> queue=new PriorityQueue<ListNode>(lists.size(),comp);
}
或者
PriorityQueue<ListNode> pq = new PriorityQueue<>(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val-o2.val;
}
});
刷题
合并K个排序链表
合并k个排序链表,并且返回合并后的排序链表。
思路,把所有链表放到一个优先队列,然后将优先队列序列化成链表。
代码
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) return null;
PriorityQueue<ListNode> q = new PriorityQueue<ListNode>(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val-o2.val;
}
});
for (int i = 0; i < lists.length; i++) {
if (lists[i] != null) {
q.add(lists[i]);
}
}
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
while (!q.isEmpty()) {
tail.next = q.poll();
tail = tail.next;
if (tail.next != null) {
q.add(tail.next);
}
}
return dummy.next;
}
数组中的第K个最大元素
public int findKthLargest(int[] nums, int k) {
// 正确性判断
if (0 == nums.length || null == nums || k <= 0 || k > nums.length) {
return -1;
}
// 构造优先队列,默认为最小堆,传入自定义的比较器转换成最大堆
PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b - a);
for (Integer num : nums) {
pq.add(num);
}
for (int i = 0; i < k - 1; i++) {
pq.remove();
}
return pq.peek();
}
数据流中位数
数字是不断进入数组的,在每次添加一个新的数进入数组的同时返回当前新数组的中位数。
中位数的定义:
- 这里的
中位数
不等同于数学定义里的中位数
。 - 中位数是排序后数组的中间值,如果有数组中有n个数,则中位数为A[(n-1)/2]。
- 比如:数组A=[1,2,3]的中位数是2,数组A=[1,19]的中位数是1。
挑战:
时间复杂度为O(nlogn)
方法2:一个最小堆,一个最大堆
思路:
//求数据流的中位数,给定的数组是代表数据流,每增加一个数,动态计算当前数据集合中的中位数
1.初始化两个堆,最大堆和最小堆。最大堆放数据中较小的那部分数据,最小堆放数据中较大的那部分数据,
2.每次增加一个元素时,都先加载到最大堆,然后判断当前集合中元素的个数,
2.1如果是奇数,比较最大堆堆顶元素和最小堆堆顶元素大小
2.1.1 如果最大堆堆顶元素 > 最小堆堆顶元素, 说明最小部分中有大于最大部分的元素存在,需要交换两个堆的堆顶元素
2.1.2 如果最大堆堆顶元素 <= 最小堆堆顶元素,说明正常,无需其他操作
2.2如果是偶数,从最大堆中分一个最大值给最小堆,保持两个堆的个数相差<=1
3.count++
public class Solution {
/**
\* @param nums: A list of integers
\* @return: the median of numbers
*/
private PriorityQueue<Integer> maxheap,minheap;
private int count=0;
public int[] medianII(int[] nums) {
maxheap=new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
minheap=new PriorityQueue<>();
int len=nums.length;
int []result=new int[len];
for(int i=0;i<len;i++){
addNums(nums[i]);
result[i]=getMedian();
}
return result;
}
public void addNums(int x){
maxheap.offer(x);
//数组中有奇数个元素时
if(count%2==0){
if(minheap.isEmpty()){
count++;
return;
}else{
if(maxheap.peek()>minheap.peek()){
int maxNum=maxheap.poll();
int minNum=minheap.poll();
maxheap.offer(minNum);
minheap.offer(maxNum);
}
}
}else{
minheap.offer(maxheap.poll());
}
count++;
}
public int getMedian(){
return maxheap.peek();
}
}