堆排序原理和示意图不多说了,网上各种有。总结下其最核心的思想:
(1)自底向上,自右向左遍历建堆。这里的底不是指最后一个节点,而是最后一个非叶子节点。每个非叶子节点与其左儿子与右儿子(假如有的话)相比,如果父节点小,那么将左右儿子中较大的那个与父节点交换,然后递归调整被交换的儿子所在的子树,让其继续满足堆性质。
(2)自顶向下调整,使得整个二叉树和其任意子树都满足堆特性。内容和上面建堆中的递归调整一样。注意调整子树时只用调整被交换的那个,而不用左右子树都递归调整。
堆排序算法算是遍历和递归结合的一种典型了。算法中注意父节点和叶子节点的索引对应,感觉绕脑的话用父节点为0,左儿子1,右儿子2多对照下。
下面上算法,具体注释都写在代码里了:
public class HeapSort {
public static void main(String[] args) {
int[] array = {2,1,10,2,-1,7,4,78,-100};
maxHeapSort(array);
for (int i : array) {
System.out.print(i + " ");
}
}
public static void maxHeapSort(int[] array) {
// 建堆,从最后一个非叶子节点开始,自底向上,自右向左遍历建堆
for (int i = array.length/2 - 1; i >= 0; i--) {
adjustMaxHeap(array, i, array.length -1);
}
// 交换头尾元素并调整堆(最大堆,最大元素在前面,交换后到最后面,形成从小到大排序)
for (int i = array.length - 1; i > 0 ; i--) {
swap(array, 0, i); // 交换元素
adjustMaxHeap(array, 0, i -1);
}
}
public static void adjustMaxHeap(int[] array, int begin, int end) {
int leftIndex = 2 * begin + 1; // 左儿子索引
int rightIndex = 2 * begin + 2; // 右儿子索引
boolean hasLeftChild = leftIndex <= end; // 是否左儿子
boolean hasRightChild = rightIndex <= end; // 是否有右儿子
if (hasLeftChild && hasRightChild) {
// 同时存在左右儿子时,左儿子比右儿子和父节点都要大
if (array[leftIndex] >= array[rightIndex] && array[leftIndex] > array[begin]) {
swap(array, begin, leftIndex); // 左儿子上浮
adjustMaxHeap(array, leftIndex, end); //调整左子树堆
}
// 同时存在左右儿子时,右儿子比左儿子和父节点都要大
else if (array[rightIndex] >= array[leftIndex] && array[rightIndex] > array[begin]) {
swap(array, begin, rightIndex); // 右儿子上浮
adjustMaxHeap(array, rightIndex, end); // 调整右子树堆
}
}
// 只存在左儿子而且大于父节点
else if (hasLeftChild && array[leftIndex] > array[begin]) {
// 左儿子上浮,此时不用递归调整左子树,因为堆是完全二叉树,左右儿子不满时说明这是最后一个父节点
swap(array, begin, leftIndex);
}
}
// 交换函数,原理:a ^ a = 0, a ^ 0 = a
private static void swap(int[] array, int i, int j) {
array[i] = array[i] ^ array[j];
array[j] = array[i] ^ array[j];
array[i] = array[i] ^ array[j];
}
}