堆排序整体思路:
1. 初始化最大堆
2. push堆顶元素倒最后,并将堆长度减一,直到堆长度为0
首先几个关于堆的知识:
1. 堆就是完全二叉树, 最大堆的特性: 任意的父节点总比其两个子节点大.
2. 堆排序原理:
- 先将数组整理成一个最大堆. (最大堆就是升序排列,最小堆就是降序排列,)
- 开始push堆顶元素,将最后一个元素放入堆顶,并重新向下整理堆,直到堆长度为0 (数组中实现就是第一个元素和最后一个元素交换位置)
- 整理一遍堆的时间复杂度是 O(logn), 第一次整理了n/2次,之后每次push操作又整理了n-1次. 故整体时间复杂度约为 O(n*logn)
3.二叉树的节点转换公式: 如果有节点下标为n,则其左子节点下标为 2n,右子节点下标为2n+1,父节点下标为 n/2
Java具体实现:
package arithmetic.sort;
import java.util.Arrays;
/**
* 堆排序 简洁优雅版
* @author jiangfeng 2019/7/29 12:45
*/
public class HeapSort2 {
public static void main(String[] args) {
int[] a = {24, 10, 5, 1, 2, 24, 5, 1, 2, 5, 7, 8, 5, 3, 7, 63, 9, 0, 42, 3, 8, 24, 1};
//int [] a = { 5, 4, 3, 2, 1 };
long start = System.currentTimeMillis();
heapSort(a);
System.out.println(Arrays.toString(a)); // 1ms
//System.out.println(JsonOutput.toJson(a));//gson里的 json output转换那个很慢. 41ms
//System.out.println(JSON.toJSONString(a));//序列化更慢了 163ms
System.out.println("用时:" + (System.currentTimeMillis() - start));
}
private static void heapSort(int[] a) {
// 1.初始化最大堆,从倒数第二层开始初始化
for (int i = a.length/2; i >=0; i--) {
sortDownHeap(a,a.length,i);
}
// 2. push堆顶元素倒最后,并且重新建立最大堆
for (int i = a.length-1; i >=0 ; i--) {
swap(a,0,i);
sortDownHeap(a,i-1,0);
}
}
/**
* 最大堆向下调整
* @param a
* @param length 堆的大小,边界
* @param i 需要调整顺序的元素
*/
private static void sortDownHeap(int[] a, int length, int i) {
// 最大堆的特性: 任意的父节点总比其两个子节点大.
// 二叉树节点转换: 如果有节点下标为n,则其左子节点下标为 2n,右子节点下标为2n+1,父节点下标为 n/2
int t = i+1; //便于理解 数组的下标序号和数组的序号的转换. 下文也需注意这点: 数组实际下标 = 堆下标 - 1;
while (2*t <= length) { // 该节点不是最后一层,存在左子节点
int tmpMax = t;// 记录父子节点中的最大下标
if (a[t - 1] < a[2 * t - 1]) { // 父节点和左子节点比较出最大值
tmpMax = 2 * t;
}
if (a[tmpMax - 1] < a[2 * t]) { // 上次比较中较大的和右子节点比较
tmpMax = 2 * t + 1;
}
if (tmpMax != t) { // 如果当前根节点不是最大值,则执行交换.
swap(a, t - 1, tmpMax - 1);
t = tmpMax;// 继续向下调整
} else {
return;// 否则停止调整
}
}
}
private static void swap(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}