1、堆的性质
堆是一棵完全二叉树,除最后一层外每层都是满的(元素个数为2^(i-1),根节点为第1层),最后一层如果不满则只缺少右边叶结点。如果按照广度优先,即从上至下,从左至右对节点编号。根节点序号为0,节点i的父节点是(i-1)/2,左子节点是2*i+1。最小堆中任意父节点不能比子节点大,最大堆中任意父节点不能比子节点小。
private static int getParent(int i){
return (i-1)/2;
}
private static int getLeftChild(int i){
return 2*i+1;
}
2、最小堆的调整规则
如果将最小堆中的顶点替换成某一个数,若这个数仍然比它的左右子节点小,则整个结构仍然是最小堆。否则将此节点与左右子节点中较小的节点发生交换,只有较小子节点所在的子树需要调整
//递回方法,不推荐
private static void adjustChildMinHeap(int[] almostHeapExceptCur,int cur,int last){
int left=getLeftChild(cur);
if(left>last) return;
int min=left;
//找出左右子节点中较小的节点
if(left<last&&almostHeapExceptCur[left+1]<almostHeapExceptCur[left]) min=left+1;
//如果较小的子节点比当前节点更小,则需要替换并调整这一子树
if(almostHeapExceptCur[min]<almostHeapExceptCur[cur]){
swap(almostHeapExceptCur, min, cur);
// 继续调整较小子节点所在子树
adjustChildMinHeap(almostHeapExceptCur,min,last);
}
}
// 非递归方法,almostHeapExceptCur数组中从pos+1到last位置已经是最小堆
private static void adjustChildMinHeap(int[] almostHeapExceptCur,int pos,int last){
//实际上pos元素只需移动到它最后应在位置,无需每次都移动,item函数中不变
int item = almostHeapExceptCur[pos];
//当前调整的节点位置,cur每次迭代后可能发生变化
int cur =pos;
//默认左子节点较小
int min = getLeftChild(cur);
while(min<=last){
//如果有右子节点,则比较两个子节点的值,min取较小节点的位置
if(min<last && almostHeapExceptCur[min+1]<almostHeapExceptCur[min]){
min = min+1;
}
//如果较小子节点比当前节点更小,则其所在子树需要调整。
if(almostHeapExceptCur[min]<item){
//将较小子节点上移到当前节点位置,相当于较小子节点位置已经为空。
almostHeapExceptCur[cur]=almostHeapExceptCur[min];
//下一次从min位置开始调整,且填充min位置元素
cur = min;
min = getLeftChild(cur);
}else{
break;
}
}
almostHeapExceptCur[cur] = item;
}
3、最小堆的建立
对于一个数组nums[N],建立规则是从最后往前按照规则调整结构使后面满足最小堆的性质。实际上,没有子节点的叶节点是无须调整的,即我们可以从getParent(N)节点开始调整。
private static void initMinHeap(int[] nums){
for(int i=getParent(nums.length-1);i>=0;i--){
adjustChildMinHeap(nums,i,nums.length-1);
}
}
4、利用最小堆排序
因为最小堆的性质,根节点是堆中最小的。我们可以每次将根节点取出来放入已排序序列。同时堆结构被破坏,根节点没有值,我们可以将最后一个节点放入根节点,也就是交换根节点与最后节点,然后继续调整结构使之成为最小堆。
public static void minHeapSortToDes(int[] nums){
initMinHeap(nums);
int last=nums.length-1;
while(last>0){
swap(nums, 0, last);
adjustChildMinHeap(nums,0,--last);
}
}