优先队列
接口
接口见 队列的Java实现
基于***二叉堆*** 的实现
原理
实现一个完全二叉树,并且保证父级节点大于等于它的子节点(最大的元素优先出队)或者保证父级节点小于等于它的子节点(最小元素优先出队)
构造一个这样的二叉堆需要如下步骤:
- 在入队时:
- 将入队元素放入堆的末尾
- 将新入队的元素与它的父级节点进行比较,如果满足优先条件,则将他们的交换位置
- 以此进行步骤2的操作直至新入队元素不满足优先条件或者到达堆顶
- 在出队时:
- 将堆顶的元素与堆尾的元素互换位置
- 删除堆尾元素
- 将新的堆顶元素与它的子节点进行比较,如果不满足优先条件,则将它与子节点满足优先条件的元素交换位置
- 以此进行步骤3的操作直至新的堆顶元素满足优先条件或者到达堆的边界
命题
- 对于一个含有 N N N 个元素的基于堆的优先队列,插入元素 操作只需要不超过 l o g 2 N + 1 log_2N+1 log2N+1 次比较,删除堆顶元素 的操作需要不超过 2 l o g 2 N 2log_2N 2log2N 次比较
最大的元素优先出队
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* 基于二叉堆的优先队列实现
* 最大的元素优先出队
*/
public class BiMaxPq<Key extends Comparable<Key>> implements IQueue<Key> {
// 当一颗二叉树的每个节点都大于等于它的子节点时,它被称为堆有序
// 根节点是堆有序的二叉树的最大节点
// 二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级存储(不使用数组的第一个位置)
/**
* 基于堆的完全二叉树
*/
private Key[] pq;
/**
* 存储于 pq[1..high] 中,pq[0] 没有使用
*/
private int high = 0;
/**
* 创建一个优先队列
*/
BiMaxPq() {
this(1);
}
/**
* 创建一个最大容量为max的有限队列
*
* @param max
* 最大容量
*/
BiMaxPq(int max) {
pq = (Key[])new Comparable[max + 1];
}
/**
* 使用 a[] 中的元素创建一个有限队列
*
* @param a
*/
BiMaxPq(Key[] a) {
high = a.length;
pq = (Key[])new Comparable[high + 1];
for (int i = 0; i < high; i++) {
pq[i + 1] = a[i];
}
for (int k = high / 2; k >= 1; k--) {
sink(k);
}
}
/**
* 向优先队列插入一个元素
*
* @param v
*/
@Override
public void enqueue(Key v) {
if (isFull()) {
// 如果数组满了,将数组扩容一倍
resize(pq.length * 2);
}
pq[++high] = v;
swim(high);
}
/**
* 将最大的元素 上浮
*
* 原理: 比对 当前元素 和 它的父级元素的大小;如果大于等于父级元素则与父级元素交换位置;对于 完全二叉树 k 节点的 父节点为 k/2
*
* @param k
* 指针位置
*/
private void swim(int k) {
while (k > 1 && less(k / 2, k)) {
exchange(k / 2, k);
k = k / 2;
}
}
/**
* 返回最大元素
*
* @return 最大元素
*/
public Key max() {
return pq[1];
}
/**
* 删除并返回最大元素
*
* @return 最大元素
*/
@Override
public Key dequeue() {
Key max = pq[1];
exchange(1, high--);
pq[high + 1] = null;
sink(1);
// 当栈大小小于数组的四分之一,将数组大小减半
// 保证数组半满,下次需要改变数组大小之前仍能进行多次的 push() 和 pop()
if (high > 0 && high <= (pq.length / 4)) {
resize(pq.length / 2);
}
return max;
}
/**
* 调正数组的大小
*
* @param max
* 新的数组大小
*/
private void resize(int max) {
// 实现逻辑为将栈移动到另一个大小不同的数组中
Key[] temp = (Key[])new Comparable[max];
for (int i = 0; i <= high; i++) {
temp[i] = pq[i];
}
pq = temp;
}
/**
* 返回栈是否已满
*
* @return boolean
*/
private boolean isFull() {
return high >= pq.length - 1;
}
/**
* 将较小的元素下沉
*
* 原理:父节点与它的较大的子节点比较,如果小于这个较大的子节点, 那么他们交换位置.
* 对于完全二叉树的节点 k 它的子节点为 2k 和 2k + 1
*
* @param k
* 元素指针
*/
private void sink(int k) {
while (2 * k <= high) {
int j = 2 * k;
if (j < high && less(j, j + 1)) {
j++;
}
if (!less(k, j)) {
break;
}
exchange(k, j);
k = j;
}
}
private boolean less(int i, int j) {
return pq[i].compareTo(pq[j]) < 0;
}
private void exchange(int i, int j) {
Key tmpKey = pq[i];
pq[i] = pq[j];
pq[j] = tmpKey;
}
/**
* 返回队列是否为空
*
* @return 队列是否为空
*/
@Override
public boolean isEmpty() {
return high == 0;
}
/**
* 返回队列中元素的个数
*
* @return 队列中元素的个数
*/
@Override
public int size() {
return 0;
}
@Override
public Iterator<Key> iterator() {
return new HeapIterator();
}
private class HeapIterator implements Iterator<Key> {
private BiMaxPq<Key> copy;
HeapIterator() {
copy = new BiMaxPq<>(size());
for (int i = 1; i <= high; i++) {
copy.enqueue(pq[i]);
}
}
@Override
public boolean hasNext() {
return !copy.isEmpty();
}
@Override
public Key next() {
if (!hasNext()) {
throw new NoSuchElementException("已经没有元素可以迭代!");
}
return copy.dequeue();
}
@Override
public void remove() {
throw new UnsupportedOperationException("不支持remove操作!");
}
}
}
最小元素优先出队
只需要调整 sink 和 swim 相关逻辑即可
/**
* 将较大的元素 上浮
* <p>
* 原理: 比对 当前元素 和 它的父级元素的大小;如果小于父级元素则与父级元素交换位置
* 对于 完全二叉树 k 节点的 父节点为 k/2
*
* @param k
* 指针位置
*/
private void swim(int k) {
while (k > 1 && gt(k / 2, k)) {
exchange(k / 2, k);
k = k / 2;
}
}
/**
* 将较大的元素下沉
* <p>
* 原理:父节点与它的较小的子节点比较,如果大于这个较小的子节点, 那么他们交换位置.
* 对于完全二叉树的节点 k 它的子节点为 2k 和 2k + 1
*
* @param k
* 元素指针
*/
private void sink(int k) {
while (2 * k <= high) {
int j = 2 * k;
if (j < high && gt(j, j + 1)) {
j++;
}
if (!gt(k, j)) {
break;
}
exchange(k, j);
k = j;
}
}
private boolean gt(int i, int j) {
return pq[i].compareTo(pq[j]) > 0;
}