堆与PriortyQueue
堆
- 堆是完全二叉树
- 大顶堆(大根堆)——每个结点的值都大于等于其左右孩子结点的值
- 小顶堆(小根堆)——每个结点的值都小于等于其左右孩子节点的值
逻辑实现:
物理实现:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
PriortyQueue
基本结构
PriorityQueue逻辑上是堆结构(平衡二叉树),物理实现(实际储存)是数组
the two children of queue[n] are queue[2n+1] and queue[2(n+1)]
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
//默认用于数组初始化大小
private static final int DEFAULT_INITIAL_CAPACITY = 11;
/**
* Priority queue represented as a balanced binary heap: the two
* children of queue[n] are queue[2*n+1] and queue[2*(n+1)]. The
* priority queue is ordered by comparator, or by the elements'
* natural ordering, if comparator is null: For each node n in the
* heap and each descendant d of n, n <= d. The element with the
* lowest value is in queue[0], assuming the queue is nonempty.
*/
//存储结点信息的数组
transient Object[] queue; // non-private to simplify nested class access
/**
* The number of elements in the priority queue.
*/
//数组中实际存放元素的个数
private int size = 0;
/**
* The comparator, or null if priority queue uses elements'
* natural ordering.
*/
//比较器
private final Comparator<? super E> comparator;
/**
* The number of times this priority queue has been
* <i>structurally modified</i>. See AbstractList for gory details.
*/
//记录修改次数的变量
transient int modCount = 0; // non-private to simplify nested class access
基本操作(增+删+出队)
添加
添加步骤
- 将新元素添加到堆结构的末尾
- 不断调整直至满足堆结构
调用add()或者offer(),实际上add()也是调用的offer(),二者效果相同
public boolean add(E e) {
return offer(e);
}
/**
* Inserts the specified element into this priority queue.
*
* @return {@code true} (as specified by {@link Queue#offer})
* @throws ClassCastException if the specified element cannot be
* compared with elements currently in this priority queue
* according to the priority queue's ordering
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size; //获取queue实际容量
if (i >= queue.length)
grow(i + 1); //扩容
size = i + 1;
if (i == 0) //如果queue为空,直接插入新元素
queue[0] = e;
else //如果不为空,插入新元素并调整
siftUp(i, e);
return true;
}
// 根据情况(当前size)扩容
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
//根据比较器调用相应的函数
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1; //获得第k结点的父节点索引
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)//满足堆结构
break;
//当不满足堆结构时
queue[k] = e;
k = parent;
}
queue[k] = x;
}
删除
1、删除的是头结点
- 用最后一个元素替换头部元素
- 用头元素和两个孩子中值较小的节点相比较,如果小于该节点的值则满足堆结构,不做任何调整,否则交换之后做同样的判断
2、删除的是中间结点
- 用最后一个元素替换将要被删除的元素并删除最后元素
- 判断该节点的值与其子节点中最小的值比较,如果小于最小值则维持堆结构,否则向下调整
- 判断该节点的值是否小于父节点的值,如果小于则向上调整,否则维持堆结构
实现删除结点的主要方法是removeAt(),还有remove()方法也是调用的removeAt()
private E removeAt(int i) {
// assert i >= 0 && i < size;
modCount++;
int s = --size;
if (s == i) // 如果要删除的结点是最后一个结点,直接删除
queue[i] = null;
else { //要删除的结点不是最后一个结点,用最后一个结点的来覆盖要删除的结点
E moved = (E) queue[s];//最后一个节点
queue[s] = null;
siftDown(i, moved); //向下调整
/*如果向下调整后,移动过结点,就不用再向上调整了*/
if (queue[i] == moved) { //向下都满足堆,未调整,则需要向上调整
siftUp(i, moved); //向上调整
if (queue[i] != moved)//说明向上调整移动过结点
return moved;
}
}
return null;
}
出队
peek()获得对根节点,poll()弹出堆顶元素
public E poll() {
if (size == 0)
return null;
int s = --size;
modCount++;
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x);//向下调整
return result;
}
运用例子
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平值。
package 数据流中的中位数;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
* 用一个大根堆+一个小根堆解决
*/
public class Solution {
static int count = 0;
static PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
static PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(
new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
public void Insert(Integer num) {
count++;
if(count % 2 == 1) {//奇数,插入大根堆
minHeap.offer(num);
Integer temp = minHeap.poll();
maxHeap.offer(temp);
} else {//偶数,插入小根堆
maxHeap.offer(num);
Integer temp = maxHeap.poll();
minHeap.offer(temp);
}
}
public Double GetMedian() {
if(count % 2 == 1) {//奇数
return new Double(maxHeap.peek());
} else {//偶数
return new Double((minHeap.peek() + maxHeap.peek())) / 2;
}
}
public static void main(String[] args) {
Solution s = new Solution();
//[5,2,3,4,1,6,7,0,8]
s.Insert(5);
System.out.print(s.GetMedian()+", ");
s.Insert(2);
System.out.print(s.GetMedian()+", ");
s.Insert(3);
System.out.print(s.GetMedian()+", ");
}
}