基于堆的优先队列
基于堆的优先队列
许多应用程序要处理有序的元素,但是不一定要求他们全部有序。许多情况下我们会收集一些元素,处理当前最大的元素,再收集更多的元素。再收集更多元素,再处理当前最大值。这种情况下,一种合适的数据结构可以支持两种操作,删除最大值+插入元素,这就是我们所说的优先队列。
堆的定义
-
完全二叉树 ,堆的定义需要用到完全二叉树的定义,那么什么是完全二叉树呢?二叉树的深度为h,除第h层外,其他各层结点都达到最大数,第h层结点都连续集中在最左边。大小为n的完全二叉树高度为logN
完全二叉树可以直接由数组存储。根结点的位置为1(这里为我们不用数组的位置0,方便后续编写程序)。子节点为位置2,3.子节点的子节点位置在4,5,6,7.) -
堆有序 ,当一颗二叉树的每个结点都大于等于它的两个子结点时,被称为堆有序。
-
二叉堆 即堆 。是堆有序的完全二叉树。此时从一个叶子结点向上是大概递增(可能会有某两个相邻结点相等)的。从一个父结点向下是大概递减的。
-
堆的存储结构
堆本质上是一颗特殊的完全二叉树,我们用数组按照层级存储。从位置1开始。位置k结点的父结点为 k/2(向下取整)。位置k的子结点为 2k,2k+1。移动也很简单pq[k]向上一层为pq[k/2],向下一层为pq[2k]。
堆的算法
对于堆的操作,我们会改变堆当前的状态,比如改变当前结点的大小,使其不再是堆有序的,我们利用上浮或下沉操作进行堆的有序化。
堆的有序化过程分为两种情况:由下至上(上浮),有上至下(下沉)
1.由下至上的堆有序化(上浮):若当前结点比它的父结点大,我们交换这两个结点,交换之后还需要判断当前结点与父结点大小,重复上述过程直至堆变得有序化。
代码实现
private void swim(int k) {
while(k > 1 && less(k/2, k)) {
exchange(k, k/2);
k = k/2;
}
}
2.有上至下的堆有序化(下沉):若当前结点变得比它的两个子结点之一更小了,我们就把当前结点与两个子结点中大的那一个进行交换。我们以不断相同的方式重复上述过程,直到它的子结点都比它小。
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)) {
exchange(k, j);
}else {
break;
}
k = j;
}
}
函数less 与exchange
private boolean less(int i, int j) {
return pq[i].compareTo(pq[j]) < 0;
}
private void exchange(int i,int j) {
Key tmp = pq[i];
pq[i] = pq[j];
pq[j] = tmp;
}
3.堆元素的删除最大值及插入元素
删除最大值就是将数组中第一个元素与最后一个元素交换,并将第一个元素进行下沉(sink)操作,以恢复堆的有序。
private Key delMax() {
Key maxKey = pq[1];
//将第一个与最后一个交换,并将最后一个位置前移
exchange(1, N--);
pq[N] = null;
sink(1);
return maxKey;
}
插入元素既在数组末尾添加元素,并将这个元素进行上浮(swim)操作
private void insert(Key value) {
pq[N+1] = value;
N++;
swim(N);
}
堆排序
堆排序分为两个阶段: 堆的构造与堆的删除最大元素过程
1.堆的构造有N个给定元素构造一个堆是一个递归过程,我们可以从右至左的用sink()函数构造子堆
2.删除最大元素我们删除最大元素后,放入堆缩小后的位置,最后形成的数组即为有序的。
public class HeapSort {
/**
*
* @param a 输入,数组
* @param k 在堆中的第k位置,存在a[k-1]
* @param N 数组长度,堆的大小
*/
private static void sink(int[] a ,int k ,int N) {
while (2*k <= N) {
int p = k-1;
int j = 2*k-1;
if (a[j] < a[j+1] && j+1 < N) {
j++;
}
if (a[p] < a[j]) {
int tmp = a[p];
a[p] = a[j];
a[j] = tmp;
}else {
break;
}
k = j+1;
}
}
}
public static int[] sort(int[] a) {
int N = a.length;
int tmp = 0;
//构造堆,从N/2开始,对堆中的每一个元素进行下沉
for (int k = N/2; k >= 1; k--) {
sink(a, k, N);
}
//处理最大元素,其实也是sink操作
while (N >=1) {
tmp = a[N-1];
a[N-1] = a[0];
a[0] = tmp ;
sink(a, 1, --N);
}
return a;
}
}
3.测试
public static void main(String[] args) {
int[] m = {15,16,9,14,10,10,7,18,4};
int[] res = sort(m);
for (int i : res) {
System.out.println(i);
}
4.**结果**
4
7
9
10
10
14
15
16
18