在了解堆之前,需要了解一种数据结构:优先队列。
普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。因此,优先队列必须支持两种操作:删除最大元素和插入元素。
而堆就是一种可以高效地实现删除最大元素和插入元素的数据结构。
堆 通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树。
将根节点最大的堆叫做 最大堆 或大根堆,根节点最小的堆叫做 最小堆 或小根堆。
一般只需要用数组来存储堆中的元素就可以了,在一个堆中,位置k的结点的父结点的位置为⌊k/2⌋,而它的两个子结点的位置则分别为2k和2k+1,因此可以通过计算数组的索引在树中上下移动:从a[k]向上一层就令k等于k/2,向下一层就令k等于2k或2k+1。
由堆的概念可知,堆中各结点的值需要按一定规律排列,为了使堆中各结点的值按最大堆或者最小堆那样排序,在堆这种数据结构中有两种重要操作(以最大堆为例):
1.如果堆的有序状态因为某个结点变得比它的父结点更大而被打破,那么就需要通过交换它和它的父结点来修复堆,即上升操作。
private void swim(int k){
while(k>1 && less(k/2,k)){
exch(k/2,k);
k = k/2;
}
}
这里补充一下堆实现的比较和交换方法
private boolean less(int i,int j){
return pq[i].compareTo(pq[j]) < 0;
}
private void exch(int i,int j){
Key t = pq[i];
pq[i] = pq[j];
pq[j] = t;
}
2.如果堆的有序状态因为某个结点变得比它的两个子结点或者其中之一更小了而被打破了,那么可以通过将它和它的两个子结点中的较大者交换来恢复堆,即下沉操作。
private void sink(int k){
while(2*k<=N){//当位置k的结点有子结点
int j = 2*k;
if(j < N && less(j,j+1)){//索引j为两个子结点中的较大者
j++;
}
if(!less(k,j)){//当位置为k的结点大于其最大的子结点时,不用再下沉了
break;
}
exch(k,j);
k = j;
}
}
最大堆进行升序排序的基本思想:
① 初始化堆:将数列a[1…n]构造成最大堆。
② 交换数据:将a[1]和a[n]交换,使a[n]是a[1…n]中的最大值;然后将a[1…n-1]重新调整为最大堆。 接着,将a[1]和a[n-1]交换,使a[n-1]是a[1…n-1]中的最大值;然后将a[1…n-2]重新调整为最大值。 依次类推,直到整个数列都是有序的。
public static void heapsort(Comparable[] a){
int N = a.length-1;
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 boolean less(Comparable v,Comparable w){
return v.compareTo(w) < 0;
}
private static void exch(Comparable[] a,int i,int j){
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
//元素上浮操作
public static void swim(Comparable[] a,int k){
while (k > 1 && less(a[k/2],a[k])){//其若父节点小于此节点
exch(a,k/2,k);
k = k/2;
}
}
//元素下沉操作
public static void sink(Comparable[] a,int k,int N){
while (2*k <= N){//若位置为k的节点有子节点
int j = 2*k;
if (j < N && less(a[j],a[j+1])){//索引j为两个子节点中的较大者
j++;
}
if (!less(a[k],a[j])){//若当前节点大于其最大的子节点,则不用再下沉了
break;
}
exch(a,k,j);//否则交换当前节点和其最大子节点的位置
k = j;
}
}