【算法】堆排序一点也不难

130 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情

如果把一个数组排成一个完全二叉树的形式,那么就可以根据完全二叉树的特点,用算法实现堆排序。

完全二叉树的几个公式

已知数组,将其转换为完全二叉树后:

  1. 最后一个非叶结点的下标是arr.length/2-1
  2. 下标为i的结点的左孩子下标为i*2+1
  3. 下标为i的结点的右孩子下标为i*2+2

堆排序的思想

将一个无序数组形成的完全二叉树转化为根堆。根据根堆的特点,大根堆的最顶端——也就是整个数组中最大元素。我们将这个最大元素与完全二叉数的最后一个叶子节点互换位置。然后再将长度减一的新二叉树重新调整为一个根堆。重复这个过程。就能够将一个无序的完全二叉数转化为有序的完全二叉数。

所以堆排序有两个过程:
一是初始建堆。将一个无序的完全二叉树建立成一个大根堆。
二是不断调整头节点的位置。始终保持二叉树是一个大根堆。
最后就实现了将数组转化为升序数组。

先打好框架:

private static void Heap(int[] arr) {
    //1.初始建堆
    //从第一个非叶子节点开始。调整以该节点为头节点的子树为一个大根堆的形状。
    for(int i= arr.length/2-1; i>=0; i--){
        Heapify(arr, i, arr.length-1);
    }
    //2.堆排序,不断调整头节点,始终保持大根堆。
    //从最后一个结点开始,不断将其和头节点交换位置。然后将新完全二叉树调整为新的大根堆
    for (int i = arr.length-1; i>0; i--) {
        swap(arr,0,i);
        Heapify(arr,0,i-1);
    }
}

接下来就是写两个函数的功能了:
swap函数很简单,就是交换数组中两个元素的值。

private static void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

接下来只要完成调整大根堆功能的函数——heapify函数,他会在两处被调用。
一是在其内部进行递归调用,作用是在调整的过程中,可能会出现头节点和左右孩子交换位置的情况。那么将不满足一个大根堆的要求。这时候需要对子树进行调整,这里就用到了递归的特点。
二是在初始建堆时的函数中从第一个非叶子节点开始将子树进行调整。

private static void Heapify(int[] arr, int i, int lastIndex) {
    //将i节点和左右孩子比较,找出最大的放在i节点的位置。
    int max = i;
    if (2*i+1<=lastIndex && arr[max]<arr[2*i+1]){
        max = 2*i+1;
    }
    if (2*i+2<=lastIndex && arr[max]<arr[2*i+2]){
        max = 2*i+2;
    }
    //如果发生了调整,就要将子树重新调整为一个根堆。
    if (max!=i){
        swap(arr,max,i);
        Heapify(arr,max,lastIndex);
    }
}

堆排序的特点。

  1. 不稳定。
  2. 时间复杂度为 Onlog2nOnlog_{2}n