金九银十之堆排序

141 阅读2分钟

什么是堆排序

将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次最大值。如此反复执行,就能得到一个有序序列了。这个过程其实就是先构建一个最大/最小二叉堆,然后不停的取出最大/最小元素(头结点),插入到新的队列中,以此达到排序的目的。

时间,空间

时间:o(n*logn)

空间:O(1)

代码实现

public static void heapSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    for (int i = 0; i < arr.length; i++) {
        //将一个数组变成堆
        heapInsert(arr, i);
    }
    int size = arr.length;
    swap(arr, 0, --size);
    while (size > 0) {
        //将已经变成堆的数组的0位置保证是最大的
        heapIfy(arr, 0, size);
        //交换位置,并将最大值放到数组的后面,最大的值交换后会跟堆断开连接,
        // 每次堆的节点会减少
        swap(arr, 0, --size);
    }
}

public static void heapInsert(int[] arr, int index) {
    //当堆中,子节点大于父节点就进行交换,
    while (arr[index] > arr[(index - 1) / 2]) {
        //进行交换
        swap(arr, index, (index - 1) / 2);
        //当前位置就到了父节点 继续循环
        index = (index - 1) / 2;
    }
}

public static void heapIfy(int[] arr, int index, int size) {
    //找到左节点
    int left = index * 2 + 1;
    while (left < size) {
        //找到两个子节点中最大的节点的角标
        int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
        //最大的子节点跟父节点进行比较得出最大值
        largest = arr[largest] > arr[index] ? largest : index;

        if (largest == index) {
            //经过比较之后已经得到了谁最大,就返回
            break;
        }
        //子节点比父节点大,位置交换
        swap(arr, largest, index);
        //记录index位置
        index = largest;
        //继续找下一个左节点
        left = index * 2 + 1;
    }
}

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

执行过程

堆排序.2021-09-26 23_01_43.gif

是否稳定

不稳定

我们知道堆的结构是节点i的孩子为2 * i和2 * i + 1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n / 2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n / 2 - 1, n / 2 - 2, … 1这些个父节点选择元素时,就会破坏稳定性。有可能第n / 2个父节点交换把后面一个元素交换过去了,而第n / 2 - 1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。