12.归并排序:分治思想的稳定排序算法

一、什么是归并排序?

归并排序(Merge Sort)是一种基于分治思想 的稳定排序算法,由 John von Neumann 在 1945 年提出。它的核心思想是先将数组递归拆分成单个元素,再将有序的子数组两两合并,最终得到完全有序的数组

简单来说,归并排序就像 "拆积木再拼起来":

  • 把复杂的大数组拆成一个个小数组,直到每个子数组只有一个元素(天然有序);
  • 再把这些有序的子数组两两合并,逐步拼成一个完整的有序数组。

二、归并排序的核心步骤

归并排序的核心步骤可以分为以下两步:

  1. 拆分(Divide):将数组从中间分成两个子数组,递归拆分直到每个子数组只有一个元素;
  2. 合并(Merge):将两个有序的子数组合并成一个更大的有序数组,重复这个过程直到所有子数组合并成一个完整的数组。

三、归并排序的代码实现

1. Python 版本(直观易懂)

复制代码
def merge_sort(arr, left, right):
    # 递归终止条件:子数组只有一个元素
    if left >= right:
        return
    # 1. 拆分:从中间分成两个子数组
    mid = (left + right) // 2
    merge_sort(arr, left, mid)
    merge_sort(arr, mid + 1, right)
    # 2. 合并:将两个有序子数组合并
    merge(arr, left, mid, right)

def merge(arr, left, mid, right):
    # 定义左右子数组
    L = arr[left:mid+1]
    R = arr[mid+1:right+1]
    # 定义三个指针:i遍历左子数组,j遍历右子数组,k遍历原数组
    i = j = 0
    k = left
    # 比较左右子数组的元素,将较小的元素放入原数组
    while i < len(L) and j < len(R):
        if L[i] <= R[j]:
            arr[k] = L[i]
            i += 1
        else:
            arr[k] = R[j]
            j += 1
        k += 1
    # 处理左子数组剩余的元素
    while i < len(L):
        arr[k] = L[i]
        i += 1
        k += 1
    # 处理右子数组剩余的元素
    while j < len(R):
        arr[k] = R[j]
        j += 1
        k += 1

# 测试
arr = [12, 11, 13, 5, 6, 7]
n = len(arr)
merge_sort(arr, 0, n - 1)
print("排序后的数组:", arr)  # 输出:[5, 6, 7, 11, 12, 13]

2. C 语言版本(更贴近底层)

复制代码
#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];
    }
    // 定义三个指针:i遍历左子数组,j遍历右子数组,k遍历原数组
    int i = 0, j = 0, k = left;
    // 比较左右子数组的元素,将较小的元素放入原数组
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k] = L[i];
            i++;
        } else {
            arr[k] = R[j];
            j++;
        }
        k++;
    }
    // 处理左子数组剩余的元素
    while (i < n1) {
        arr[k] = L[i];
        i++;
        k++;
    }
    // 处理右子数组剩余的元素
    while (j < n2) {
        arr[k] = R[j];
        j++;
        k++;
    }
    // 释放临时数组
    free(L);
    free(R);
}

// 归并排序函数
void merge_sort(int arr[], int left, int right) {
    if (left < right) {
        // 1. 拆分:从中间分成两个子数组
        int mid = left + (right - left) / 2;
        merge_sort(arr, left, mid);
        merge_sort(arr, mid + 1, right);
        // 2. 合并:将两个有序子数组合并
        merge(arr, left, mid, right);
    }
}

// 打印数组
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);

    merge_sort(arr, 0, n - 1);

    printf("排序后:");
    printArray(arr, n); // 输出:5 6 7 11 12 13

    return 0;
}

四、归并排序的时间复杂度和空间复杂度

  • 时间复杂度:O (n log n),因为每次拆分将数组分成两部分,递归深度为 log n,每层合并的时间复杂度为 O (n);
  • 空间复杂度:O (n),需要额外的临时数组存储子数组;
  • 稳定性:稳定,因为合并时会优先选择左子数组的元素,保持相同元素的相对位置。

五、归并排序的优化

为了提高归并排序的效率,可以进行以下优化:

  1. 小数组优化:当子数组的大小小于某个阈值(如 10)时,使用插入排序代替归并排序,因为插入排序在小规模数据上效率更高;
  2. 原地归并:使用原地归并算法,减少额外的内存空间使用,但会增加时间复杂度;
  3. 迭代实现:使用迭代代替递归,减少递归调用栈的深度,避免栈溢出。

六、归并排序的实际应用场景

归并排序是一种稳定、高效的排序算法,常见场景包括:

  1. 大规模数据排序:归并排序的时间复杂度稳定为 O (n log n),适合大规模数据排序;
  2. 外部排序:当数据量太大无法全部加载到内存时,使用归并排序进行外部排序;
  3. 稳定性要求高的场景:比如排序的元素是对象,需要保持相同元素的相对位置;
  4. 链表排序:归并排序适合链表排序,因为链表的合并操作不需要额外的内存空间。

七、总结

归并排序是一种基于分治思想的稳定排序算法,它的核心思想是先将数组递归拆分成单个元素,再将有序的子数组两两合并,最终得到完全有序的数组。

归并排序的时间复杂度为 O (n log n),空间复杂度为 O (n),是一种稳定的排序算法,适合大规模数据排序和稳定性要求高的场景。

希望这篇文章能帮助你理解归并排序的原理和实现!

相关推荐
吴声子夜歌1 小时前
ApacheCommons——commons-text(模板替换与文本算法)
java·开发语言·算法·apache
JiNan.YouQuan.Soft1 小时前
数值计算引擎:计算固体力学
人工智能·算法·机器学习
和裕1 小时前
光伏储能定制加强型瓦楞纸箱:降低组件运输隐裂率与售后损耗的核心价值
大数据·运维·网络·人工智能·算法
quantdash_cc2 小时前
量化数据源怎么选:稳定性、实时性和数据完整性到底该如何权衡?
开发语言·python·数据分析·量化交易·股票数据·quantdash
萧瑟余晖2 小时前
Java深入解析篇五十四之对象模型详解
java·开发语言
Pointer Pursuit2 小时前
C++11特性(二)
前端·c++·算法
lvwangshu2 小时前
数位 DP 进阶
算法·动态规划
她的男孩2 小时前
一个开源低代码框架的协作 SPI 是怎么设计的 ForgeAdmin 拆解 + 实战接入新平台
后端·算法·架构
张文是假的啊2 小时前
Java | record | Controller逻辑
java·开发语言