• 堆排序超详细讲解C语言



    堆排序是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。

    算法步骤

    先将数组变成堆,然后将堆顶元素与数组尾元素交换,然后数组向左收缩,重新建堆……依此类推。

    动图演示

    img

    静图演示

    image-20220828171347007

    代码实现

    堆的向上调整法

    void AdJustUp(HeapDateType* array, int child){
        int parent = (child - 1) / 2;
        while(child > 0){
            if(array[child] < array[parent]){
                Swap(&array[child], &array[parent]);
                child = parent;
                parent = (child - 1) / 2;
            }
            else{
                break;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    堆的向下调整法

    void AdJustDown(HeapDateType* array, int arraySize, int parent){
        int child = parent * 2 + 1;
        while(child < arraySize){
            if(child + 1 < arraySize && array[child] > array[child + 1]){
                child++;
            }
            if(array[child] < array[parent]){
                Swap(&array[child], &array[parent]);
                parent = child;
                child = parent * 2 + 1;
            }
            else{
                break;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    想要了解数据结构堆以及向上、向下调整法,请移步顺序二叉树(堆)与链式二叉树的C语言实现

    方式一

    需要用到数据结构——堆,(升序)建小堆,然后将数组元素全部插入进堆中,再依次取堆顶元素放入数组中。

    但这种方式极其不好:

    建堆的时间复杂度为 O ( N ∗ l o g N ) O(N*log^{N}) O(NlogN)空间复杂度为 O ( N ) O(N) O(N)

    排序的时间复杂度为 O ( N ∗ l o g N ) O(N*log^{N}) O(NlogN)空间复杂度为 O ( 1 ) O(1) O(1)

    若待排数据量庞大,则有可能不能一次性加载到内存中

    void HeapSort(int* a, int n){
        Heap obj;
        HeapInit(&obj);
        for(int i = 0; i < n; i++){
            HeapPush(&obj, a[i]);
        }
        for(int i = 0; i < n; i++){
            a[i] = HeapTop(&obj);
            HeapPop(&obj);
        }
        HeapDestroy(&obj);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    方式二

    不利用数据结构——堆,而是直接对数组进行建堆,利用堆的向调整法

    建堆的时间复杂度为 O ( N ∗ l o g N ) O(N*log^{N}) O(NlogN)空间复杂度为 O ( 1 ) O(1) O(1)

    排序的时间复杂度为 O ( N ∗ l o g N ) O(N*log^{N}) O(NlogN)空间复杂度为 O ( 1 ) O(1) O(1)

    但也不推荐,因为有更快的方式三

    void HeapSort(int* a, int n){
        for(int i = 0; i < n; i++){
            AdJustUp(a, i);
        }
        for(int i = n - 1; i > 0; i--){
            Swap(&a[0], &a[i]);
            AdJustDown(a, i, 0);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    方式三

    直接对数组进行建堆,利用堆的向调整法

    建堆的时间复杂度为 O ( N ) O(N) O(N)空间复杂度为 O ( 1 ) O(1) O(1)

    排序的时间复杂度为 O ( N ∗ l o g N ) O(N*log^{N}) O(NlogN)空间复杂度为 O ( 1 ) O(1) O(1)

    void HeapSort(int* a, int n){
        for(int i = (n - 1 - 1) / 2; i >= 0; i--){
            AdJustDown(a, n, i);
        }
        for(int i = n - 1; i > 0; i--){
            Swap(&a[0], &a[i]);
            AdJustDown(a, i, 0);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    复杂度、稳定性分析

    堆排序一般只用方式三,这里仅对方式三进行分析

    1. 时间复杂度

      • 建堆:

        image-20220828175101135

        第一层, 2 0 2^{0} 20个节点,需要向下移动 h − 1 h-1 h1层;

        第二层, 2 1 2^{1} 21个节点,需要向下移动 h − 2 h-2 h2层;

        第三层, 2 2 2^{2} 22个节点,需要向下移动 h − 3 h-3 h3层;

        第四层, 2 3 2^{3} 23个节点,需要向下移动 h − 4 h-4 h4层;

        ……

        h − 1 h-1 h1层, 2 h − 2 2^{h-2} 2h2个节点,需要向下移动 1 1 1层;

        则需要移动节点总的移动步数为:

        T ( n ) = 2 0 ∗ ( h − 1 ) + 2 1 ∗ ( h − 2 ) + 2 2 ∗ ( h − 3 ) + . . . 2 h − 3 ∗ 2 + 2 h − 2 ∗ 1 T(n) = 2^{0}*(h-1)+2^{1}*(h-2)+2^{2}*(h-3)+...2^{h-3}*2+2^{h-2}*1 T(n)=20(h1)+21(h2)+22(h3)+...2h32+2h21

        计算得:

        T ( n ) = n − l o g 2 n + 1 ≈ n T(n) = n - log_2^{n+1}\approx n T(n)=nlog2n+1n

        因此建堆的时间复杂度为 O ( N ) O(N) O(N)

      • 排序:

        每次将堆顶元素放入数组尾时都会进行一次堆的向下调整,而由于堆的向下调整时间复杂度为 O ( l o g N ) O(log^{N}) O(logN)

        排序的时间复杂度为 O ( N ∗ l o g N ) O(N*log^{N}) O(NlogN)

    2. 空间复杂度

      仅仅使用了常数个辅助单元,空间复杂度是O(1);

    3. 稳定性

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

      因此堆排序是不稳定的

  • 相关阅读:
    vite vue引入svg图标及封装 (轻松上手)
    【Java】LocalDate类
    数字化转型与制造企业绿色创新质量——基于供需双侧机制的再检验(2011-2022年)
    使用jdk自带的VisualVM分析hprof堆转储文件
    Unity3d shader实现消融效果
    计算机毕业设计 基于SSM的垃圾分类管理系统(以医疗垃圾为例)的设计与实现 Java实战项目 附源码+文档+视频讲解
    jvm 内存结构 ^_^
    界面控件DevExpress WinForms皮肤编辑器的这个补丁,你了解了吗?
    函数和存储过程的区别
    C# 类型转换
  • 原文地址:https://blog.csdn.net/qq_67569905/article/details/126574350