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),是一种稳定的排序算法,适合大规模数据排序和稳定性要求高的场景。

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

相关推荐
来让爷抱一个2 小时前
Python 异步里混用同步库,事件循环被卡住的全过程
开发语言·python
Dream it possible!4 小时前
LeetCode 面试经典 150_多维动态规划_交错字符串(97_C++_中等)
算法·leetcode·面试·动态规划
心抵鹊4 小时前
算法之双指针系列(一)
算法·leetcode
十二4 小时前
【洛谷题解】P1083 [NOIP 2012 提高组] 借教室(二分答案 + 差分)
数据结构·c++·算法·蓝桥杯
心祭丫丫5 小时前
【每天一个 Python 小案例17】Python 实现通讯录
开发语言·人工智能·笔记·python·学习
露天赏雪5 小时前
第3篇:Sentinel熔断降级实战:大模型接口抖动时的服务兜底方案
java·开发语言·人工智能·spring boot·spring·servlet·sentinel
小此方5 小时前
[算法实战论:动态规划]回文串问题的dp解法,回文串问题的母题,以及以此为基础的派生题
算法·动态规划
Pniubi6 小时前
力扣560.和为k的子数组
算法·leetcode·职场和发展
D e re k ZH6 小时前
C++ 从零入门(一):基础语法与程序结构
数据结构·c++·算法
Pniubi7 小时前
力扣438.找到字符串中所有字母异位词
算法·leetcode·职场和发展