高级排序算法(二):归并排序与堆排序详解

引言

在上一章中,我们探讨了高效的快速排序及其分治思想。这一次,我们将继续探索两种同样重要的排序算法:归并排序(Merge Sort)堆排序(Heap Sort)

它们与快速排序一样,都是**O(n log n)**时间复杂度的排序算法,但实现方式和适用场景却各有不同。归并排序在稳定性和分布式场景中表现优异,而堆排序则在内存有限的情况下表现突出。

接下来,我们将一起深入剖析这两种排序算法的原理、实现与应用。


一、归并排序(Merge Sort)

1.1 算法思想

归并排序是典型的分治法应用,采用"分而治之"的思想,递归地将数组分成两部分,分别排序后再合并。

1.2 算法过程
  1. 分解:将数组从中间分成两部分,直到每部分只有一个元素(显然是有序的)。
  2. 合并:将两个有序的子数组合并成一个有序数组。
  3. 递归:对每个子数组重复上述过程。
1.3 C语言实现
cpp 复制代码
​
#include <stdio.h>
#include <stdlib.h>

// 合并两个有序子数组
void merge(int arr[], int left, int mid, int right) {
    int n1 = mid - left + 1;  // 左子数组的大小
    int n2 = right - mid;     // 右子数组的大小

    // 创建临时数组
    int* L = (int*)malloc(n1 * sizeof(int));
    int* R = (int*)malloc(n2 * sizeof(int));

    // 复制数据到临时数组
    for (int i = 0; i < n1; i++) L[i] = arr[left + i];
    for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j];

    // 合并临时数组到原数组
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k++] = L[i++];
        } else {
            arr[k++] = R[j++];
        }
    }

    // 复制剩余元素
    while (i < n1) arr[k++] = L[i++];
    while (j < n2) arr[k++] = R[j++];

    // 释放临时数组
    free(L);
    free(R);
}

// 归并排序
void mergeSort(int arr[], int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;  // 防止溢出
        mergeSort(arr, left, mid);           // 排序左半部分
        mergeSort(arr, mid + 1, right);      // 排序右半部分
        merge(arr, left, mid, right);        // 合并两部分
    }
}

int main() {
    int arr[] = {38, 27, 43, 3, 9, 82, 10};
    int n = sizeof(arr) / sizeof(arr[0]);

    mergeSort(arr, 0, n - 1);

    printf("排序后的数组: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

​
1.4 时间和空间复杂度
  • 时间复杂度
    • 每次分解需要log n层。
    • 每次合并需要O(n)时间。
    • 总体时间复杂度为O(n log n)
  • 空间复杂度
    • 由于需要额外的临时数组,空间复杂度为O(n)
1.5 优点与缺点
  • 优点
    • 时间复杂度始终为O(n log n),无最坏情况。
    • 稳定排序,适用于需要保持顺序的场景。
  • 缺点
    • 需要额外的存储空间,空间复杂度较高。

二、堆排序(Heap Sort)

2.1 算法思想

堆排序是基于堆数据结构 的排序算法,通过构建最大堆最小堆实现排序。

  • 最大堆:堆顶(根节点)是堆中最大的元素。
  • 最小堆:堆顶(根节点)是堆中最小的元素。

堆排序的过程可以分为两个步骤:

  1. 构建堆:将无序数组调整为一个最大堆。
  2. 排序:依次将堆顶元素(最大值)与最后一个元素交换,并调整堆。
2.2 C语言实现

构建堆的核心:向下调整(Heapify)

cpp 复制代码
​
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) {
        int temp = arr[i];
        arr[i] = arr[largest];
        arr[largest] = temp;
        heapify(arr, n, largest);
    }
}

​

堆排序主函数

cpp 复制代码
​
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--) {
        // 将当前堆顶元素(最大值)移到数组末尾
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;

        // 调整剩余部分,重新构建堆
        heapify(arr, i, 0);
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6, 7};
    int n = sizeof(arr) / sizeof(arr[0]);

    heapSort(arr, n);

    printf("排序后的数组: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

​
2.3 时间和空间复杂度
  • 时间复杂度
    • 构建堆需要O(n)时间。
    • 排序过程需要O(n log n)时间。
    • 总体时间复杂度为O(n log n)
  • 空间复杂度
    • 原地排序,无额外空间需求,空间复杂度为O(1)
2.4 优点与缺点
  • 优点
    • 时间复杂度为O(n log n),无最坏情况。
    • 不需要额外存储空间,适合内存有限的场景。
  • 缺点
    • 不稳定排序。
    • 实现相对复杂,调整堆时需要更多代码。

三、对比与总结

特性 归并排序 堆排序
时间复杂度 O(n log n) O(n log n)
空间复杂度 O(n) O(1)
稳定性 稳定 不稳定
适用场景 数据量大,且对稳定性有要求 内存有限,且对稳定性无要求

四、总结与展望

归并排序和堆排序是经典的高级排序算法,各有优缺点。归并排序因其稳定性和良好的最坏情况表现适用于广泛场景,而堆排序因其原地排序特性在内存有限的情况下更具优势。

在下一篇文章中,我们将深入探讨线性时间排序算法(如计数排序、桶排序、基数排序),感受算法在特定场景下的极致效率,敬请期待!

归并排序与堆排序不仅是经典的排序算法,也是面试中经常被问到的重点。希望通过这篇文章,你能深入理解它们的原理与实现,为掌握高级排序算法迈出扎实的一步。

有疑问或建议?欢迎评论区讨论,我们一起进步!

相关推荐
博界IT精灵6 分钟前
C语言之查漏补缺
c语言
维齐洛波奇特利(male)6 分钟前
(前序 简单)leetcode 226翻转二叉树
算法·leetcode·职场和发展
网络安全指导员24 分钟前
渗透利器-kali工具 (第五章-4) Metasploit漏洞利用模块一
开发语言·网络·python
GOTXX35 分钟前
基于MATLAB的图像增强
开发语言·图像处理·人工智能·计算机视觉·matlab·音视频·超分辨率重建
怀澈12237 分钟前
geeCache 一致性hash
算法·哈希算法
每天写点bug39 分钟前
【go每日一题】 channel实现mutex锁
开发语言·后端·golang
工业甲酰苯胺40 分钟前
Python随机抽取Excel数据并在处理后整合为一个文件
开发语言·python·excel
长潇若雪1 小时前
《深入探究:C++ 在多方面对 C 语言实现的优化》
c语言·开发语言·c++·经验分享
LateBloomer7771 小时前
顺序队列的实现及其应用
数据结构·笔记·学习·循环队列
旅行者星期日1 小时前
02-51单片机的C语言基础与最小系统
c语言·嵌入式硬件·51单片机·proteus