堆排序超详细讲解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;
        }
    }
}

堆的向下调整法

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;
        }
    }
}

想要了解数据结构堆以及向上、向下调整法,请移步顺序二叉树(堆)与链式二叉树的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);
}

方式二

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

建堆的时间复杂度为 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);
    }
}

方式三

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

建堆的时间复杂度为 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. 时间复杂度

    • 建堆:

      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个相同的元素之间的稳定性就被破坏了;

    因此堆排序是不稳定的

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
当然,下面是堆排序算法C语言实现代码: ```c #include <stdio.h> // 交换两个元素的值 void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } // 调整堆 void heapify(int arr[], int n, int i) { int largest = i; // 将当前节点标记为最大值 int left = 2 * i + 1; // 左子节点的索引 int right = 2 * i + 2; // 右子节点的索引 // 如果左子节点大于根节点,则更新最大值索引 if (left < n && arr[left] > arr[largest]) largest = left; // 如果右子节点大于根节点,则更新最大值索引 if (right < n && arr[right] > arr[largest]) largest = right; // 如果最大值不是根节点,则交换根节点和最大值,并递归调整子树 if (largest != i) { swap(&arr[i], &arr[largest]); heapify(arr, n, largest); } } // 堆排序函数 void heapSort(int arr[], int n) { // 构建堆(使数组成为一个最大堆) for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i); // 一个个从堆中取出元素 for (int i = n - 1; i > 0; i--) { // 将当前最大值(根节点)与数组末尾元素交换 swap(&arr[0], &arr[i]); // 重新调整堆,排除已排序的元素 heapify(arr, i, 0); } } // 打印数组 void printArray(int arr[], int n) { for (int i = 0; i < n; ++i) printf("%d ", arr[i]); printf("\n"); } int main() { int arr[] = { 12, 11, 13, 5, 6, 7 }; int n = sizeof(arr) / sizeof(arr[0]); printf("原始数组:"); printArray(arr, n); heapSort(arr, n); printf("排序后的数组:"); printArray(arr, n); } ``` 这是一个基于数组的堆排序算法的实现代码。首先,通过`heapify`函数将输入数组转换为最大堆。然后,依次从堆中取出最大值(根节点),并将其与当前未排序部分的末尾元素交换。最后,对交换后的堆进行重新调整,排除已排序的元素。重复该过程直到整个数组排序完成。 请注意,这只是一个简单的例子,用于展示堆排序算法的实现原理。实际使用时,可能需要进行更多的错误处理和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云朵c

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值