我们在学习优先队列之前我们先来看看他和普通队列的区别
普通队列:先进先出,后进后出
优先队列:出队顺序和入队顺序无关,和优先级有关。动态选择优先级高的执行
不论是普通队列 还是优先队列,他们的本质还是一个队列 所以我们可以复用原来的队列的接口。我们也可以使用多种数据结构作为优先队列的底层实现结构,我们今天学的堆就是其中的一种底层实现结构。
二叉堆(堆本身就是一颗二叉树)
1.完全二叉树(把元素一层一层的放,知道放完为止)
2.堆中某个节点的值总是不大于其父亲节点的值(最大堆)
3…堆中某个节点的值总是不小于其父亲节点的值(最小堆)
用数组存储二叉堆
每个父亲节点的左孩子索引 = 父亲节点索引 * 2 +1;
每个父亲节点的右孩子索引 = 父亲节点索引 * 2 + 2;
每个父亲节点索引 = (孩子节点-1) / 2;
要注意该节点有没有父亲节点,具体的实现代码如下
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("索引为0,没有父亲节点");
}
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);
}
}
从堆中取出元素(只能取出最大的元素)
我们可以很容易的找到并取出最大的元素(数组下标为0),我们取出元素还要把它的左右子树有机结合在一起,所以我们需要把最后一个元素当做它的父亲节点,然后与左右孩子比较 看看是否大于左右孩子节点,如果小于则交换位置,交换完位置后还得继续与新的孩子节点进行比较,我们以此类推,最终达到了从堆中取出元素这种操作要注意最后一个元素顶替原来的根节点那么要注意让最后节点位置元素消失(二叉树不能有重复元素)还要注意二叉堆中是否有元素。具体的实现代码如下:
//看堆中的最大元素
public E findMax() {
if(data.getSize() == 0) {
throw new IllegalArgumentException("不能从空堆中找到最大的元素");
}
return data.get(0);
}
//取出堆中最大的元素
public E extractMax() {
E ret = findMax();
data.swap(0, data.getSize() - 1);
data.removeLast();
siftDown(0);
return ret;
}
private void siftDown(int k ) {
while(leftChild(k) < data.getSize()) {
int j = leftChild(k);
if(j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0) {
j = rightChild(k);
}
//data[j] 是leftChild 和 rightChild 中的最大值
if(data.get(k).compareTo(data.get(j)) >= 0) {
break;
}
data.swap(k, j);
k = j;
}
}
取出堆中的元素并替换成e,具体的实现代码如下:
//取出堆中最大的元素,并且替换成元素e
public E replace(E e) {
E ret = findMax();
data.set(0, e);
siftDown(0);
return ret;
}
将任意数组整理成堆的形状
将数组看做完全二叉树,从倒数第一个非叶子节点来看从后往前进行下沉操作(交换父亲节点和孩子节点进行比较之后)他的位置只要获得最后一个元素的索引 根据上面的公式求出他的父亲节点即可具体实现代码如下
//heapify操作
public MaxHeap(E[] arr) {
data = new Array<>(arr);
for(int i = parent(arr.length - 1); i >= 0; i-- ) {
siftDown(i);
}
}
public Array(E[] arr) {
data =(E[]) new Object[arr.length];
for(int i = 0; i < arr.length; i++) {
data[i] = arr[i];
}
size = arr.length;
}
用堆实现优先队列(复用队列接口)
具体实现代码如下
public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {
private MaxHeap<E> maxHeap;
public PriorityQueue() {
maxHeap = new MaxHeap<>();
}
@Override
public int getSize() {
return maxHeap.size();
}
@Override
public boolean isEmpty() {
return maxHeap.isEmpty();
}
@Override
public E getFront() {
return maxHeap.findMax();
}
@Override
public void enqueue(E e) {
maxHeap.add(e);
}
@Override
public E dequeue() {
return maxHeap.extractMax();
}
}