二叉堆有一个很特殊的节点 —堆顶,堆顶是所有节点的最大元素,或者是最小元素,这主要取决于这个二叉堆是最小堆还是最大堆。基于堆顶这个特点,来实现堆排序。
用辅助数组来实现堆排序算法
如图有10个节点元素的二叉堆:
堆顶这个节点删除,然后把删除的节点放在一个辅助数组help里(黑色的节点表示已经被删除没用的点)。
被删除的节点,是堆中值最小的节点。然后继续删除二叉堆的堆顶,然后把删除的元素还是存放在help数组里。
第二次删除的节点,是原始二叉堆中的第二小节点。继续重复删除堆顶。
继续连续6次删除堆顶,把删除的节点一次放入help数组。
二叉堆中只剩最后一个节点了,这个节点同时也是原始二叉堆中的最大节点,把这个节点继续删除了,还是放入help数组里。
二叉堆的元素被删除光了,观察一下help数组。这是一个有序的数组,实际上,通过从二叉堆的堆顶逐个取出最小值,存放在另一个辅助的数组里,当二叉堆被取光之时,就完成了一次堆排序了。
无需辅助数组
在上面的堆排序过程中,使用了一个辅助数组help。可事实上,不需要辅助数组也能实现。
二叉堆的实现就是采取数组的形式来存储的。
从二叉堆中删除一个元素,为了充分利用空间,可以把删除的元素直接存放在二叉堆的最后一个元素那里的。例如:
删除堆顶,把删除的元素放在最后一个元素。
节点,把删除的元素放在最后第二个位置
继续删除
以此类推….
这样,对于一个含有n个元素的二叉堆,经过n-1(不用删除n次)次删除之后,这个数组就是一个有序数组了。
所以,给定无序的数组,需要先把这个数组构建成二叉堆,然后在通过堆顶逐个删除的方式来实现堆排序。
其实,也不算是删除了,相当于是把堆顶的元素与堆尾部在交换位置,然后在通过下沉的方式,把二叉树恢复成二叉堆。
代码如下:
import java.util.Arrays;
/**
* 堆排序
* 堆的时间复杂度是 O (nlogn)
* 空间复杂度是 O(1)
* @author madonghao
* @date 2018/10/24
*/
public class HeapSort {
/**
* 下沉操作,执行删除操作相当于把最后
* 一个元素赋给根元素之后,然后对根元素执行下沉操作
* @param arr 数组
* @param parent 要下沉元素的下标
* @param length 数组长度
* @return
*/
public static int[] downAdjust(int[] arr, int parent, int length) {
//临时保证要下沉的元素
int temp = arr[parent];
//定位左孩子节点位置
int child = 2 * parent + 1;
//开始下沉
while (child < length) {
//如果右孩子节点比左孩子小,则定位到右孩子
if(child + 1 < length && arr[child] > arr[child + 1]) {
child++;
}
//如果父节点比孩子节点小或等于,则下沉结束
if(temp <= arr[child]){
break;
}
//单向赋值
arr[parent] = arr[child];
parent = child;
child = 2 * parent + 1;
}
arr[parent] = temp;
System.out.println("每次执行的结果:" + Arrays.toString(arr));
return arr;
}
/**
* 堆排序
* @param arr 数组
* @param length 数组长度
* @return
*/
public static int[] heapSort(int[] arr, int length) {
//构造二叉堆
for(int i = (length - 2) / 2; i >= 0; i--) {
arr = downAdjust(arr, i, length);
}
//进行堆排序
for(int i = length - 1; i >= 1; i--) {
//把堆顶的元素与最后一个元素交换
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//下沉调整
arr = downAdjust(arr, 0 , i);
}
return arr;
}
//测试用例
public static void main(String[] args) {
int[] arr = new int[]{1, 3, 5, 2, 0, 10, 6};
System.out.println("开始:" + Arrays.toString(arr));
arr = heapSort(arr, arr.length);
System.out.println("结束:" + Arrays.toString(arr));
}
}
执行的结果:
开始:[1, 3, 5, 2, 0, 10, 6]
每次执行的结果:[1, 3, 5, 2, 0, 10, 6]
每次执行的结果:[1, 0, 5, 2, 3, 10, 6]
每次执行的结果:[0, 1, 5, 2, 3, 10, 6]
每次执行的结果:[1, 2, 5, 6, 3, 10, 0]
每次执行的结果:[2, 3, 5, 6, 10, 1, 0]
每次执行的结果:[3, 6, 5, 10, 2, 1, 0]
每次执行的结果:[5, 6, 10, 3, 2, 1, 0]
每次执行的结果:[6, 10, 5, 3, 2, 1, 0]
每次执行的结果:[10, 6, 5, 3, 2, 1, 0]
结束:[10, 6, 5, 3, 2, 1, 0]