/**
* 堆排序(大顶堆)
* 将记录看成一个顺序存储的二叉树
* 先构建大顶堆,所有节点都比他的儿子大或者等于。
* 第一步:从最后一个非叶子节点开始比较,一直到根。
* 构建大顶堆成功。
* 第二步:根与最后的叶子节点交换位置
* 第三步:除去刚才的叶子节点,再次构建大顶堆,
* 这个时候构建大顶堆只要从上往下层级遍历就好。
*
* 算法性能:
* 堆排序,他的运行时间主要实在堆的构建和重建堆的反复筛选上
* 在构建堆的时候:我们从完全二叉树的最后一个非叶子节点开始构建
* 将他与他的孩子节点进行比较,交换。对于每一个非叶子节点来说。
* 最多就是两次比较和一次交换。所以初始化堆的时间复杂度是O(n)
* 正式排序的时候:第i次取堆顶记录和重建堆需要O(logi)的时间。
* (完全二叉树每个节点到根的距离为log2i+1);并且需要去除n-1
* 次堆顶记录。因此重建堆的时间复杂度为O(nlogn)。
* 所以总的来说,堆排序的时间复杂度为O(nlogn)
* @param arr
*/
public static void duiSort(int[] arr) {
// 构建大顶堆
// 从最后一个非叶子节点的根节点开始比较
int len = arr.length;
// 从最后一个叶子节点的根节点开始比较,根始终是最大的
// 因为数组从0开始计数,所以最后一个叶子节点的根节点要减一(arr.length / 2 - 1)
// 从下往上,每一次找出最大的放到根节点,变成最大堆
for (int i = (arr.length / 2 - 1); i >= 0; i--) {
// 根节点与叶子节点比较,
compareWithLeaf(arr, len, i);
}
// 调整堆结构,交换堆顶元素和末尾元素的值,数组结构,所以减一,到了根就不用比较了。所以i>0就行
for (int i = (len - 1); i > 0; i--) {
int swap = arr[0];
arr[0] = arr[i];
arr[i] = swap;
// 去掉堆末尾元素,继续调整
compareWithLeaf(arr, i, 0);
}
}
public static void compareWithLeaf(int[] arr, int len, int root) {
int k = root; // 记录根节点下标
int temp = arr[root]; // 记录根节点值
int index = 2 * k + 1; // 记录左叶子节点下标,因为数组从0开始计数,所以最后要加一
while (index < len) { // 在数组长度内
if (index + 1 < len) { // 有右叶子节点
if (arr[index] < arr[index + 1]) { // 如果右叶子节点大于左叶子节点的值
index = index + 1; // 保存右叶子节点的下标
}
}
if (temp < arr[index]) { // 根节点的值小于叶子节点的值
arr[k] = arr[index]; // 把叶子节点的值放在根上
k = index; // 交换位子的叶子结点当做根
index = 2 * k + 1; // 交换位子的叶子结点的左叶子节点
} else { // 不需要操作,已经满足状态
break;
}
arr[k] = temp; // 找到最终根节点的位置
}
}
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
冒泡排序 | N2 | n | N2 | 1 | 稳定 |
选择排序 | N2 | N2 | N2 | 1 | 不稳定 |
插入排序 | N2 | n | N2 | 1 | 稳定 |
希尔排序 | N*logn~n2 | N1.3 | N2 | 1 | 不稳定 |
堆排序 | N*logn | N*logn | N2 | 1 | 不稳定 |
归并排序 | N*logn | N*logn | N*logn | N | 稳定 |
快速排序 | N*logn | N*logn | N2 | 1 | 不稳定 |