堆排序,忘了好多次了,稍微记录一下。
堆排序的总体思路:
对于任意一个数组
int[] a = {4,5,1,2,9,6,3,8,7};
- 首先要把这个数组抽象成二叉树,很简单,就是按照完全二叉树的顺序画出画出各个节点
- 其次,调整这棵二叉树,让他称为一个堆,大顶堆一般用于升序
- 此时根节点一定是最大的,将根节点和末尾节点交换,相当于就是在数组中把最大值移动到最后一位
- 此后,对数组0~n-2继续上述操作,就能够把第2大元素放到倒数第2位...直到第0个元素
图解
代码:
static void heap_sort(int[] a) {
for (int k = a.length; k >= 1; k--) {
build_heap(a, k);
printArr(a);
swap(a, 0, k-1);
}
}
static void build_heap(int[] a, int n) {//从下往上,从最后的一个非叶子开始调整堆,内部每遍历一个节点调用heapify往下调整
int last_node = n - 1;
int parent = (last_node - 1) / 2;
for (int i = parent; i >= 0; i--) {
heapify(a, i, n);
}
}
static void heapify(int[] a, int i, int n) {// 从上往下,从下标为i的节点开始往下递归调整,n是数组长度(前提是后面会通过build_heap调用heapify,build_heap是从下往上,从右往左维护了合法堆的,所以调用heapify时才保证只有修改了c1,c2的情况下,谁被修改,谁才要继续递归调整堆
System.out.println("cur i = "+i);
if (i >= n) {
return ;
}
int c1 = 2 * i + 1;// 当前i的两个孩子的下标
int c2 = 2 * i + 2;
System.out.print("c1 = "+c1+" , ");
System.out.print("c2 = "+c2+" , ");
// 开始调整
int maxDx = i;
// 选取左右孩子的最大值出来,和 a[i] 交换
if (c1 < n && a[c1] > a[maxDx]) {
maxDx = c1;
}
if (c2 < n && a[c2] > a[maxDx]) {
maxDx = c2;
}
System.out.println("maxDx = "+maxDx);
if (maxDx != i) {
swap(a, i, maxDx);
// 继续往下一直到底继续递归调整,而往下的索引就是maxDx,因为i是和maxDx交换的,所以只有maxDx动了,只可能影响maxDx这条分支
heapify(a, maxDx, n);
}
}
static void swap(int[] a, int i, int j) {//每次调整完一个堆,就要把根(最大值)移动到最后
int t = a[i];
a[i] = a[j];
a[j] = t;
}
总结:
- build_heap()总体上其实是从下往上+从右往左,而heapify()相当于局部的从当前节点往下递归建立合法堆,最开始学堆排序的时候,我一直以为build_heap就足够了,以为从下往上+从右往左一个个小子树维护成堆,一直到根,这样就能保持整体是堆,但不一定,因为在上层维护堆的时候,会改变子节点,子节点堆的性质可能被破坏。
- 所以需要heapify来对每个子树调整堆。