-
优先队列的概念
- 优先队列本身用堆实现。假设已经有一堆数据在优先队列中,把他们从队列里挨个取出来的过程我们就可以称之为堆排序。
- 优先队列可以随时取队中最大值,插入等
- 优先队列:出队顺序和入队顺序无关,和优先级有关。动态选择优先级高的执行
- 普通队列:先进先出,后进后出
上浮:
如果父节点比k结点小,就交换他们的位置
代码实现:
private void swim(int k) {
while (k > 1 && less(k/2, k)) {
exch(k, k/2);
k = k/2;
}
}
下沉
如果k结点比它的两个子结点小,则与较大的那个交换
代码实现:
private void sink(int k) {
while (2*k <= N) {
int j = 2*k;
if (j < N && less(j, j+1)) j++;
if (!less(k, j)) break;
exch(k, j);
k = j;
}
}
凭借以上两种方法我们可以构建出一个平衡的二叉树
插入操作:我们在二叉树末尾插入结点,再调用swim()方法使他上浮到正确的位置。
删除最大值操作:我们将要删除的元素和末尾元素交换位置,再删除想要删除的元素,并调用sink()方法使被交换元素下沉到正确位置,以此来保证二叉树有序
代码实现:
public void insert(Key v) {
pq[++N] = v;
if (N == 1 || min.compareTo(v) > 0) {
min = v;
}
swim(N);
}
public Key delMax() {
Key max = pq[1];
exch(1, N--);
pq[N + 1] = null;
sink(1);
if (isEmpty()) {
min = null;
}
return max;
}
两种方法图示:
----------------------------------------------------------------------------------------
构造方法图示:
- 我们在数组中间元素的位置依次调用sink方法来使二叉树有序
- 用每次删除根节点的方法来使数组排序(经过sink方法后,父节点总是比子节点要大)
public class Heap {
public static void sort(Comparable[] a) {
int N = a.length;
for (int k = N / 2; k >= 1; k--) {
sink(a, k, N);
}
while (N > 1) {
exch(a, 1, N--);
sink(a, 1, N);
}
}
private static void sink(Comparable[] pq, int k, int n) {
while (2 * k <= n) {
int j = 2 * k;
if (j < n && less(pq, j, j + 1)) {
j++;
}
if (!less(pq, k, j)) {
break;
}
exch(pq, k, j);
k = j;
}
}
private static boolean less(Comparable[] pq, int i, int j) {
return pq[i - 1].compareTo(pq[j - 1]) < 0;
}
private static void exch(Object[] pq, int i, int j) {
Object swap = pq[i - 1];
pq[i - 1] = pq[j - 1];
pq[j - 1] = swap;
}
- 性能分析:
- 堆排序使我们所知的唯一一种可以同时利用时间和空间的方法,在最坏情况下它也能够保证用~2NlgN此比较和恒定的额外空间,当空间十分紧张时(比如嵌入式系统)它很流行
- 是一种不稳定的排序,是原地排序
- 时间复杂度:NlogN
- 空间复杂度:1