lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

堆排序

1.  什么是堆排序?

堆排序是一种基于堆这种数据结构的排序算法。堆是一种特殊的完全二叉树,它满足以下性质:

  • 大顶堆:每个非叶子节点的值都大于或等于其左右子节点的值,根节点的值是最大的。
  • 小顶堆:每个非叶子节点的值都小于或等于其左右子节点的值,根节点的值是最小的。

堆排序的思想是利用大顶堆或小顶堆来选出最大或最小的元素,然后将其放到数组的末尾,再对剩余的元素重复这个过程,直到数组有序。

2.  堆排序的步骤

堆排序的步骤如下:

  1. 构建初始堆,将待排序数组调整为一个大顶堆(升序)或小顶堆(降序)。
  2. 将堆顶元素与数组末尾元素交换,并从数组中移除。
  3. 对剩余的数组重新调整为堆。
  4. 重复步骤2和3,直到数组只剩一个元素。

3.  堆排序的示例

假设我们要对以下数组进行升序排序:

[16, 7, 3, 20, 17, 8]

我们可以按照以下步骤进行:

  1. 构建初始大顶堆,
    • 从最后一个非叶子节点开始,自下而上,自右而左调整每个节点,使其满足大顶堆性质。
    • 最后一个非叶子节点是16,它已经满足大顶堆性质,所以不需要调整。
    • 倒数第二个非叶子节点是7,它的右子节点20比它大,所以交换它们的位置。
    • 然后继续调整20所在的子树,发现它的左子节点17比它大,所以再次交换它们的位置。
    • 此时得到一个大顶堆:

[20, 17, 16, 7, 3, 8]

  1. 将堆顶元素20与数组末尾元素8交换,并从数组中移除20。此时数组变为:

[8, 17, 16, 7, 3] | [20]

  1. 对剩余的数组重新调整为大顶堆。从根节点开始,比较它与左右子节点的大小,如果有比它大的子节点,就交换它们的位置,并继续调整交换后的子树。此时得到一个新的大顶堆:

[17, 8, 16, 7, 3] | [20]

  1. 将堆顶元素17与数组末尾元素3交换,并从数组中移除17。此时数组变为:

[3, 8, 16, 7] | [17, 20]

  1. 对剩余的数组重新调整为大顶堆。得到:

[16, 8, 3, 7] | [17, 20]

  1. 将堆顶元素16与数组末尾元素7交换,并从数组中移除16。此时数组变为:

[7, 8, 3] | [16, 17, 20]

  1. 对剩余的数组重新调整为大顶堆。得到:

[8, 7, 3] | [16, 17, 20]

  1. 将堆顶元素8与数组末尾元素3交换,并从数组中移除8。此时数组变为:

[3, 7] | [8, 16, 17, 20]

  1. 对剩余的数组重新调整为大顶堆。得到:

[7, 3] | [8, 16, 17, 20]

  1. 将堆顶元素7与数组末尾元素3交换,并从数组中移除7。此时数组变为:

3 | [7, 8, 16, 17, 20]

  1. 对剩余的数组重新调整为大顶堆。得到:

3 | [7, 8, 16, 17, 20]

  1. 将堆顶元素3与数组末尾元素3交换,并从数组中移除3。此时数组变为:

[] | [3, 7, 8, 16, 17, 20]

排序完成。

 

4. 堆排序的Java实现

以下是一个简单的Java实现,参考了

public class HeapSort {

    public static void main(String[] args) {
        int[] arr = {16, 7, 3, 20, 17, 8};
        heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void heapSort(int[] arr) {
        //构建初始大顶堆
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            adjustHeap(arr, i, arr.length);
        }
        //交换堆顶和末尾元素,重新调整堆
        for (int j = arr.length - 1; j > 0; j--) {
            swap(arr, 0, j);
            adjustHeap(arr, 0, j);
        }
    }

    public static void adjustHeap(int[] arr, int i, int len) {
        //保存当前节点的值
        int temp = arr[i];
        //找到左子节点的位置
        int k = i * 2 + 1;
        //遍历所有子节点
        while (k < len) {
            //如果有右子节点,且右子节点大于左子节点,选择右子节点
            if (k + 1 < len && arr[k] < arr[k + 1]) {
                k++;
            }
            //如果子节点大于父节点,将子节点赋值给父节点
            if (arr[k] > temp) {
                arr[i] = arr[k];
                //继续比较下一层子节点
                i = k;
                k = i * 2 + 1;
            } else {
                //如果父节点大于子节点,退出循环
                break;
            }
        }
        //将原来的父节点值赋值给最终的子节点
        arr[i] = temp;
    }

    public static void swap(int[] arr, int i, int j) {
        //交换两个元素的位置
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

5. 堆排序的性能分析

堆排序的时间复杂度是O(nlogn),因为每次调整堆的时间复杂度是O(logn),而调整堆的次数是O(n)。

堆排序的空间复杂度是O(1),因为只需要一个额外的变量来交换元素。

堆排序是一种不稳定的排序算法,因为在交换堆顶和末尾元素时,可能会破坏相同元素的相对位置。

6. 参考资料

  • https://www.cnblogs.com/luomeng/p/10618709.html
  • https://blog.csdn.net/d875708765/article/details/108531476
  • https://blog.csdn.net/allway2/article/details/114093981
posted on 2023-07-03 21:26  白露~  阅读(21)  评论(0编辑  收藏  举报