堆的概念:
堆是一个完全二叉树,又分为最小堆和最大堆。最大堆指的是每个节点的值总是大于其子结点。
如下图所示,就为一个最大堆(ps.图画的不好,能说明问题)。
由于堆是一个完全二叉树,因此实际中一般采用数组存放。设父节点的下标为parentIndex,子结点的下标为childIndex,左孩子的下标为leftIndex,右孩子为rightIndex,满足如下的规律:
parentIndex=(childIndex-1)/2
leftIndex=2*parent+1
rightIndex=leftIndex+1=2*parent+2
堆排序的大体思路:
不断构建最大堆,然后将堆顶元素移动到最后,接着就剩余的元素接着构建最大堆。
具体实现过:
为了节省空间,直接把存放元素的原始数组构成一个最大堆。
第一步,构建最大堆,从最后一个父节点开始往前遍历,把每个父节点当做头结点完成下潜(即,先保存头结点的值,将大于头节点的元素依次上浮,最后将之前保存的头结点的值赋给最后一次上浮的位置)。由于是从最后一个父节点依次往前遍历的,这样使得之后每次下潜的过程中总能保证此时的头结点下面的子结点可以构成一个最大堆。
第二步,依次将堆顶元素移动到堆底,堆长度减一,接着把此时的堆底元素作为堆顶完成下潜如此便可以得到新的最大堆,重复上述步骤..,最终使得堆内只有一个元素,完成排序。
代码如下:
public static void predown(int[] data,int head,int leng) {
//下潜
int parent=head;
int child=parent*2+1;
int temp=data[parent];
for(;(parent*2+1)<=leng-1;parent=child) {
//获得最大的孩子
child=parent*2+1;
if(child+1<=leng-1) {
if(data[child]<data[child+1]) {
child++;
}
}
if(data[child]>temp) {//把大于头的孩子往上提
data[parent]=data[child];
}else {
break;
}
}
data[parent]=temp;
}
public static void heapSort(int[] data) {
//构建最大堆
for(int head=(data.length)/2-1;head>=0;head--) {
predown(data,head,data.length);
}
//交换堆顶,堆地元素,将堆长度减一,在将此时的堆顶元素下潜
int leng=data.length;//堆的实际元素个数
for(int i=0;i<data.length-1;i++) {
change(data,0,leng-1);
leng--;
predown(data,0,leng);
}
}
复杂度分析:
时间复杂度分为两部分,首次构建最大堆部分和所有元素依次下潜部分,第一部分迭代次数<N,每轮中进行上移动的次数亦<logN,因此第一部分<NlogN,第二部分,迭代次数为N数量级,每轮当中亦为logN数量级,因此其时间复杂度为O(NlogN)。
空间复杂度很显然为O(1)。
此外,构建最大堆时会破坏原有结构,因此其为一种不稳定的排序算法。