首先,我们来看看什么是堆(heap):
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树(Complete Binary Tree)。
完全二叉树是由满二叉树(Full Binary Tree)而引出来的。除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树称为满二叉树。 如果除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点,这样的二叉树被称为完全二叉树。 一棵完全二叉树,如果某个节点的值总是不小于其父节点的值,则根节点的关键字是所有节点关键字中最小的,称为小根堆(小顶堆); 如果某个节点的值总是不大于其父节点的值,则根节点的关键字是所有节点关键字中最大的,称为大根堆(大顶堆)。
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
需要注意的是,堆只对父子节点做了约束,并没有对兄弟节点做任何约束,左子节点与右子节点没有必然的大小关系。 将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值), 然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次最大值。如此反复执行,就能得到一个有序序列了。
- 最坏时间复杂度 O(nlog n)
- 最优时间复杂度 O(nlog n)
- 平均时间复杂度 (nlog n)
- 空间复杂度 O(1)
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
/**
* 堆排序的实现需要解决的两个关键问题:
* (1)将一个无序序列构成一个堆。
* (2)输出堆顶元素后,调整剩余元素成为一个新堆。
* 时间复杂度:平均:O(nlogn)、最坏:O(nlogn)、最优:O(nlogn)。
* 空间复杂度:最坏:O(n)。
*
* @param array 待排序的数组
*/
public void heapSort(int[] array) {
for (int i = 0; i < array.length; i++) {
// 每次建堆就可以排除一个元素了,每次排序完成之后,最大值总之在末尾。
maxHeapify(array, array.length - i);
// 交换,将第一个元素和当前已经排完序的元素进行交换
int temp = array[0];
array[0] = array[(array.length - 1) - i];
array[(array.length - 1) - i] = temp;
}
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
/**
* 完成一次建堆,最大值在堆的顶部(根节点)
*/
public void maxHeapify(int[] array, int size) {
// 从数组的尾部开始,直到第一个元素(角标为0)
for (int i = size - 1; i >= 0; i--) {
heapify(array, i, size);
}
}
/**
* 建堆(为什么要从最后一个值开始建堆,为什么?)
* 这是为了保障顶点一定是最大的元素,不信的话你就断点试试,或者自己用草稿本试一下,从0开始是不能保证根节点是最大的值。
*
* @param array 看作是完全二叉树
* @param currentRootNodePosition 当前父节点位置
* @param size 节点总数
*/
public void heapify(int[] array, int currentRootNodePosition, int size) {
if (currentRootNodePosition < size) {
// 左子树和右字数的位置
int left = 2 * currentRootNodePosition + 1;
int right = 2 * currentRootNodePosition + 2;
// 把当前父节点位置看成是最大的
int max = currentRootNodePosition;
if (left < size) {
// 如果比当前根元素要大,记录它的位置
if (array[max] < array[left]) {
max = left;
}
}
if (right < size) {
// 如果比当前根元素要大,记录它的位置
if (array[max] < array[right]) {
max = right;
}
}
// 如果最大的不是根元素位置,那么就交换
if (max != currentRootNodePosition) {
int temp = array[max];
array[max] = array[currentRootNodePosition];
array[currentRootNodePosition] = temp;
//继续比较,直到完成一次建堆
heapify(array, max, size);
}
}
}
/**
* 非递归方式的建堆。(二叉树的前序遍历)
*
* @param array 待排序的数组
* @param currentRootNodePosition 当前根节点的位置
* @param size 节点总数
*/
public void heapifyNotRecursive(int[] array, int currentRootNodePosition, int size) {
if (currentRootNodePosition < size) {
//左右子树的位置
int left = 2 * currentRootNodePosition + 1;
int right = 2 * currentRootNodePosition + 2;
//把当前父节点位置看成是最大的
int max = currentRootNodePosition;
if (left < size) {
if (array[max] < array[left]) {
max = left;
}
}
if (right < size) {
if (array[max] < array[right]) {
max = right;
}
}
if (max != currentRootNodePosition) {
int sum = array[max] + array[currentRootNodePosition];
array[max] = sum - array[max];
array[currentRootNodePosition] = sum - array[currentRootNodePosition];
}
}
}