Heap Sort,堆排序,对简单选择排序的一种改进
二叉堆:顺序存储
堆具有完全二叉树的性质:每个结点值都大于或等于其左右孩子结点的值,成为大顶堆;或者每个结点值都小于等于其左右孩子结点值,称为小顶堆。
大顶堆,图片来自程序员小辉灰
小顶堆,图片来源于程序员小灰
对于n个元素的序列{}当且仅当满足下列关系之一时,称之为堆
(1)且 【小根堆】
(2)且 【大根堆】
其中,i=0,1,2,...,n/2向下取整
堆的自我调整
有一下几种操作:
插入结点:二叉堆插入结点位置在完全二叉树的最后一个位置
删除结点:删除过程和插入过程相反,删除的是堆顶的元素
构建二叉堆:把一个无序的完全二叉树调整为二叉堆,本质,让所有非叶子节点依次下沉
二叉堆顺序存储在数组里,如下图,计算要依靠数组下标
图片来于程序员小灰
堆排序基本思想:将待排的序列构成一个大顶堆,整个序列的最大值就是堆顶元素-根结点。将它移走(与堆数组的末尾元素交换),然后将剩余的n-1个序列重新构成一个堆,得到n个元素中次大值,如此反复执行,得到有序序列。
堆排序算法步骤
- 把无序数组构建成二叉堆
- 循环删除对顶元素,移到集合尾部,调节堆产生新的堆顶
/**
* 堆排序
*/
public static void heapSort(int[] array) {
//1.把无序数组构造位二叉堆
for (int i = (array.length - 2) / 2; i >= 0; i--) {
downAdjust(array, i, array.length);
}
System.out.println(Arrays.toString(array));
//循环删除堆顶元素,移到集合尾部,调节堆产生新堆
for (int i = array.length - 1; i > 0; i--) {
//最后一个和第一个元素交换
int temp = array[i];
array[i] = array[0];
array[0] = temp;
//下沉调整最大堆
downAdjust(array, 0, i);
}
}
将待排序的序列构造成大顶堆,本质,从下往上,从右到左,将内阁非叶节点当作根结点,将其和其子树调整位大顶堆----注意i的变化是在调整那些元素哦
/**
* 下沉调整
*
* @param array 待调整的堆
* @param parentIndex 要下沉的父节点
* @param length 堆的有效大小
*/
public static void downAdjust(int[] array, int parentIndex, int length) {
//temp保存父节点值
int temp = array[parentIndex];
int childIndex = 2 * parentIndex + 1;
while (childIndex < length) {
//如果有右孩子且右孩子小于左孩子,则定位到右孩子
if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]) {
childIndex++;
}
//如果父节点小于任何一个孩子的值,直接跳出
if (temp <= array[childIndex]) {
break;
}
array[parentIndex] = array[childIndex];
parentIndex = childIndex;
childIndex = 2 * childIndex + 1;
}
array[parentIndex] = temp;
}
复杂度分析
第一步,把无序数组构建成二叉堆,时间复杂度位O(n)
第二步,需要进行n-1次循环,每次循环调用依次downAdjust,时间复杂度位O(nlogn)
两个步骤并列关系,所以总的时间复杂度:O(nlogn)
堆排序最好,最差,平均的时间复杂度均为O(nlogn)
空间复杂度:需要一个辅助存储单元