【排序算法】堆排序

128 阅读2分钟

堆排序,忘了好多次了,稍微记录一下。

堆排序的总体思路:

对于任意一个数组

int[] a = {4,5,1,2,9,6,3,8,7};
  1. 首先要把这个数组抽象成二叉树,很简单,就是按照完全二叉树的顺序画出画出各个节点
  2. 其次,调整这棵二叉树,让他称为一个堆,大顶堆一般用于升序
  3. 此时根节点一定是最大的,将根节点和末尾节点交换,相当于就是在数组中把最大值移动到最后一位
  4. 此后,对数组0~n-2继续上述操作,就能够把第2大元素放到倒数第2位...直到第0个元素

图解

微信图片_20220317201911.jpg

代码:

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来对每个子树调整堆。