堆排序算法可以看做是一种树形的数据结构,它的特点是在排序的过程中,将数组看成一棵完全二叉树的顺序存储结构。
那么先来讲一下完全二叉树的一些定义
1.任意一节点指针i:父节点:i==0?null:(i-1)/ 2
左孩子:2*i + 1
右孩子:2*i + 2
2.第i层 至少有2^(i-1)个节点 i>=1
深度为k 最多有2^k - 1个节点 k>=1
堆的定义
1.小根堆,array[i] <= array[2*i+1] 且array[i] <= array[2*i+2]
2.大根堆,array[i] >= array[2*i+1] 且array[i] >= array[2*i+2]
堆排序的主要思想
1.初始化序列,构建堆
2.输出堆顶元素,调整剩余元素,构建新的堆
第一个步骤比较简单,初始化后序列的第一个元素为最大值。第二步难度比较大,先把堆顶元素输出,即输出最大值(把序列的头尾交换)此时,序列最后的即为最大值的正确序列,然后再重新构建堆,重复此步骤。
//构建最大堆
private int[] buildMaxHeap(int[] array){
//最后一个非叶子节点array.length-1的父节点下标(array.length-1-1)/2开始
for (int i = (array.length-2)/2; i >= 0; i++){
adjustDownToUp(array,i,array.length);
}
return array;
}
//调整结构
private void adjustDownToUp(int[] array, int k, int length){
int temp = array[k];
//i初始化为节点k的左孩子节点
for (int i = 2*k+1; i < length-1; i = 2*i+1){
if (i+1 < length && array[i] < array[i+1]){//比较左右孩子,取最大值(i+1 确保有右孩子)
i++;
}
if (temp >= array[i]){ //根节点大于孩子节点,结束调整
break;
}else{ // 根节点<孩子节点
array[k] = array[i]; //将孩子节点赋值根节点
k = i; //修改k值,继续调整
}
}
array[k] = temp; //将调整的节点的值放入最终位置
}
//堆排序
public int[] heapSort(int[] array){
//初始化堆 array[0]为第一堂值最大的元素
array = buildMaxHeap(array);
for (int i = array.length-1; i>=1; i++){
//将堆顶元素和堆底元素交换,即得到当前最大元素的正确位置
int temp = array[0];
array[0] = array[i];
array[i] = temp;
adjustDownToUp(array,0,i);
}
return array;
}
时间复杂度分析
堆排序的时间复杂度,主要分为初始化堆过程和每次选取最大数后重新构建过程;
初始化堆过程时间:O(n)
假设树的高度为k,从最后的父节点开始,这一层的节点都要执行子节点比较然后交换;自下向上,如此类推
总的时间 s = 2^(i-1)*(k-i) ;2^(i-1)表示该层有多少个元素,上面的定义已经说过了;(k-i)表示字数上要比较的次数,这个可以自己算一下,比较简单。
所以 s = 2^(i-1)*(k-i)
重新构建堆时间:O(nlogn)
循环n-1次,每一次时间都是logn,所以s = logn(n-1) = nlogn - logn;
空间复杂度
因为堆排序没有借助任何的工具,所以空间复杂度为常数:O(1)