归并排序原理与Python实现详解

归并排序算法详解

1. 算法概述

归并排序(Merge Sort)是一种基于分治法(Divide and Conquer)的高效排序算法。该算法采用递归思想,将待排序的序列不断拆分成更小的子序列,直到每个子序列只有一个元素,然后将这些有序的子序列合并成更大的有序序列,最终完成整个序列的排序。

核心特点

  • 稳定性:归并排序是稳定的排序算法
  • 时间复杂度:始终为 O(n log n)
  • 空间复杂度:O(n),需要额外的存储空间
  • 适用场景:适合大数据量的排序,特别是链表排序

2. 算法原理与步骤

2.1 分治策略

归并排序的核心思想包含三个主要步骤:

步骤 描述 作用
分解 将序列递归地分成两半 将大问题转化为小问题
解决 对子序列进行排序 递归处理子问题
合并 将有序子序列合并 组合子问题的解

2.2 详细执行流程

python 复制代码
def merge_sort(arr):
    """
    归并排序主函数
    """
    if len(arr) <= 1:
        return arr
    
    # 分解步骤:找到中间位置
    mid = len(arr) // 2
    
    # 递归处理左右子序列
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    
    # 合并步骤:合并两个有序子序列
    return merge(left, right)

def merge(left, right):
    """
    合并两个有序序列
    """
    result = []
    i = j = 0
    
    # 比较两个序列的元素,按顺序合并
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    
    # 将剩余元素添加到结果中
    result.extend(left[i:])
    result.extend(right[j:])
    
    return result

3. 动图演示原理

虽然无法直接展示动态图像,但可以通过文字描述归并排序的动图演示过程:

初始序列[38, 27, 43, 3, 9, 82, 10]

分解过程

复制代码
第一次分解:[38, 27, 43, 3] 和 [9, 82, 10]
第二次分解:[38, 27] 和 [43, 3] | [9, 82] 和 [10]
第三次分解:[38] 和 [27] | [43] 和 [3] | [9] 和 [82] | [10]

合并过程

复制代码
第一层合并:[27, 38] 和 [3, 43] | [9, 82] 和 [10]
第二层合并:[3, 27, 38, 43] 和 [9, 10, 82]
最终结果:[3, 9, 10, 27, 38, 43, 82]

这个过程展示了归并排序如何通过递归分解和有序合并来实现排序。

4. 完整Python实现

4.1 基础版本

python 复制代码
def merge_sort_complete(arr):
    """
    完整的归并排序实现(原地修改)
    """
    if len(arr) > 1:
        # 分解步骤
        mid = len(arr) // 2
        left_arr = arr[:mid]
        right_arr = arr[mid:]
        
        # 递归排序
        merge_sort_complete(left_arr)
        merge_sort_complete(right_arr)
        
        # 合并步骤
        i = j = k = 0
        
        # 比较合并
        while i < len(left_arr) and j < len(right_arr):
            if left_arr[i] <= right_arr[j]:
                arr[k] = left_arr[i]
                i += 1
            else:
                arr[k] = right_arr[j]
                j += 1
            k += 1
        
        # 处理剩余元素
        while i < len(left_arr):
            arr[k] = left_arr[i]
            i += 1
            k += 1
        
        while j < len(right_arr):
            arr[k] = right_arr[j]
            j += 1
            k += 1

# 测试示例
test_array = [64, 34, 25, 12, 22, 11, 90]
print("原始数组:", test_array)
merge_sort_complete(test_array)
print("排序后数组:", test_array)

4.2 优化版本(减少空间占用)

python 复制代码
def merge_sort_optimized(arr, left=0, right=None, temp=None):
    """
    优化版归并排序,减少空间占用
    """
    if right is None:
        right = len(arr) - 1
    if temp is None:
        temp = [0] * len(arr)
    
    if left < right:
        mid = (left + right) // 2
        
        # 递归排序左右部分
        merge_sort_optimized(arr, left, mid, temp)
        merge_sort_optimized(arr, mid + 1, right, temp)
        
        # 合并有序序列
        merge_optimized(arr, left, mid, right, temp)

def merge_optimized(arr, left, mid, right, temp):
    """
    优化合并函数
    """
    i, j, k = left, mid + 1, left
    
    # 比较合并
    while i <= mid and j <= right:
        if arr[i] <= arr[j]:
            temp[k] = arr[i]
            i += 1
        else:
            temp[k] = arr[j]
            j += 1
        k += 1
    
    # 复制剩余元素
    while i <= mid:
        temp[k] = arr[i]
        i += 1
        k += 1
    
    while j <= right:
        temp[k] = arr[j]
        j += 1
        k += 1
    
    # 将临时数组复制回原数组
    for idx in range(left, right + 1):
        arr[idx] = temp[idx]

5. 算法复杂度分析

5.1 时间复杂度

情况 时间复杂度 说明
最好情况 O(n log n) 序列已经有序
平均情况 O(n log n) 随机序列
最坏情况 O(n log n) 序列完全逆序

推导过程

  • 分解深度:log₂n 层
  • 每层合并操作:O(n)
  • 总时间复杂度:O(n log n)

5.2 空间复杂度

  • 基础版本:O(n log n) - 递归调用栈和临时数组
  • 优化版本:O(n) - 只使用一个临时数组

6. 实际应用场景

6.1 适用场景

  1. 大数据排序:当数据量很大且内存充足时
  2. 外部排序:处理无法全部装入内存的大文件
  3. 链表排序:归并排序特别适合链表数据结构
  4. 稳定排序需求:需要保持相等元素相对位置时

6.2 不适用场景

  1. 小数据量:插入排序可能更高效
  2. 内存受限:快速排序的空间效率更高
  3. 几乎有序数据:冒泡排序或插入排序更好

7. 与其他排序算法对比

特性 归并排序 快速排序 堆排序
时间复杂度 O(n log n) O(n log n) O(n log n)
稳定性 稳定 不稳定 不稳定
空间复杂度 O(n) O(log n) O(1)
适用数据 大数据量 通用 大数据量

8. 扩展应用

8.1 逆序对计数

python 复制代码
def count_inversions(arr):
    """
    使用归并排序思想计算逆序对数量
    """
    if len(arr) <= 1:
        return arr, 0
    
    mid = len(arr) // 2
    left, inv_left = count_inversions(arr[:mid])
    right, inv_right = count_inversions(arr[mid:])
    merged, inv_merge = merge_count(left, right)
    
    return merged, inv_left + inv_right + inv_merge

def merge_count(left, right):
    """
    合并并计数逆序对
    """
    result = []
    i = j = 0
    inversions = 0
    
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            inversions += len(left) - i  # 关键步骤:计数逆序对
            j += 1
    
    result.extend(left[i:])
    result.extend(right[j:])
    
    return result, inversions

8.2 外部排序实现

归并排序是外部排序的基础,常用于处理超大规模数据集的排序问题。其基本思想是将大数据集分成多个小块,在内存中排序后写回磁盘,然后使用多路归并的方法合并这些有序块。

归并排序以其稳定的性能和可预测的时间复杂度,在需要可靠排序性能的场景中得到了广泛应用。虽然它的空间复杂度相对较高,但其稳定性和对大数据量的良好适应性使其成为重要的排序算法之一。


参考来源

相关推荐
AC赳赳老秦24 分钟前
语义采集进阶实战:利用 OpenClaw AI 语义识别自动提取网页核心信息,无需手动编写选择器
java·运维·服务器·python·信息可视化·deepseek·openclaw
Logintern0937 分钟前
什么时候应该用多进程什么时候用多线程呢?
开发语言·python
无凭1 小时前
字节跳动 DeerFlow:Agent Harness 怎么让大模型主动向用户提问?
人工智能·python
蜀道山老天师1 小时前
Python + Playwright 实现问卷星自动化填写
python
Zane19941 小时前
多开几个线程,为什么算数字反而没变快?一文讲透 CPython 的 GIL
后端·python
W_326002 小时前
Python-OpenCV边缘检测与阈值分割:Sobel、Scharr、Laplacian、Canny、全局与自适应阈值
开发语言·图像处理·python·opencv·机器学习
青 春 记 忆2 小时前
LeetCode 121. 买卖股票的最佳时机|Python 解法详解
python·算法·leetcode
En^_^Joy2 小时前
Django模板与静态文件使用指南
python·django·sqlite
满怀冰雪2 小时前
21-图像分类实战:从 MNIST 到 CIFAR-10
人工智能·python·深度学习·分类·数据挖掘·paddlepaddle
Logintern092 小时前
Celery 的底层架构正是进程池、事件循环、epoll 和协程全部组合在了一起
python·架构·消息队列·进程·celery·事件循环