当前位置: 首页 > news >正文

归并排序详解:优雅的分治艺术

什么?归并排序?这让博主想起了大学那会被《数据结构与算法》支配的恐惧…
哈哈言归正传,一直想对算法做一个专栏,因为其实工作中很少很少有机会用到算法,倒是很多工具方法底层会使用,工作被各种需求业务“折磨”的让大家很少再花费精力去深入这些底层,更别说很少有人天天刷算法,导致以前在学校为了应付面试和笔试(也包括社招)学习的这些玩意和基础随着时间慢慢消磨殆尽,所以这篇专栏来了!每一篇博主都会详细说明这些算法的设计思想和代码实现,帮助博主和大家共同进步,每篇如果有错误和需要改进的地方欢迎大家提出!下面开始我们第一篇,也是基础的——归并排序。

文章目录

  • 前言
  • 🌟 核心思想
  • ⚙️ Java实现
  • 🔍 时间复杂度分析
  • 📦 空间复杂度
  • ✅ 算法特性
  • ⚡ 优化技巧
  • 💡 实际应用场景
  • 🌐 总结


前言

归并排序是一种基于分治法的高效排序算法,由冯·诺依曼于1945年首次提出。它以其稳定的O(n log n)时间复杂度和优雅的实现方式在算法领域占据重要地位。下面我将详细介绍归并排序的原理,并提供完整的Java实现。


🌟 核心思想

先给出归并排序示意图:
归并排序

归并排序的核心思想可概括为三步:

  1. 分解:将待排序数组递归地分成两个子数组。
  2. 解决:对子数组进行排序(递归排序)。
  3. 合并:将两个有序子数组合并成一个有序数组。
原始数组:[38, 27, 43, 3, 9, 82, 10]分解过程:
1. [38, 27, 43, 3]  [9, 82, 10]
2. [38, 27] [43, 3]  [9, 82] [10]
3. [38] [27] [43] [3] [9] [82] [10]合并过程:
1. [27, 38] [3, 43]  [9, 82] [10]
2. [3, 27, 38, 43]    [9, 10, 82]
最终结果:[3, 9, 10, 27, 38, 43, 82]

⚙️ Java实现

public class MergeSort {// 归并排序主方法public static void mergeSort(int[] arr) {if (arr == null || arr.length <= 1) {return;}int[] temp = new int[arr.length];mergeSort(arr, temp, 0, arr.length - 1);}// 递归排序private static void mergeSort(int[] arr, int[] temp, int left, int right) {if (left < right) {int mid = left + (right - left) / 2; // 防止整数溢出// 递归排序左半部分mergeSort(arr, temp, left, mid);// 递归排序右半部分mergeSort(arr, temp, mid + 1, right);// 合并两个有序子数组merge(arr, temp, left, mid, right);}}// 合并两个有序子数组private static void merge(int[] arr, int[] temp, int left, int mid, int right) {// 复制数据到临时数组System.arraycopy(arr, left, temp, left, right - left + 1);int i = left;      // 左子数组起始索引int j = mid + 1;  // 右子数组起始索引int k = left;     // 合并数组的起始索引// 比较左右子数组元素,将较小者放入原数组while (i <= mid && j <= right) {if (temp[i] <= temp[j]) {arr[k++] = temp[i++];} else {arr[k++] = temp[j++];}}// 复制左子数组剩余元素while (i <= mid) {arr[k++] = temp[i++];}// 复制右子数组剩余元素while (j <= right) {arr[k++] = temp[j++];}}// 测试代码public static void main(String[] args) {int[] arr = {38, 27, 43, 3, 9, 82, 10};System.out.println("排序前: " + Arrays.toString(arr));mergeSort(arr);System.out.println("排序后: " + Arrays.toString(arr));}
}

算法解析

  1. mergeSort()方法:
    • 公共入口方法。
    • 创建临时数组避免在递归过程中重复创建。
    • 调用私有递归方法。
  2. 递归排序:
    • 计算中点:mid = left + (right - left)/2(避免整数溢出)。
    • 递归排序左半部分:left到mid。
    • 递归排序右半部分:mid+1到right。
    • 合并两个有序子数组。
  3. merge()方法:
    • 复制当前段数据到临时数组。
    • 使用三个指针分别追踪:
      • i:左子数组当前位置
      • j:右子数组当前位置
      • k:合并后数组当前位置
    • 比较左右子数组元素,将较小者放入原数组。
    • 处理剩余元素。

🔍 时间复杂度分析

操作时间复杂度
分解O(log n)
合并O(n)
总复杂度O(n log n)

归并排序在任何情况下的时间复杂度都是稳定的O(n log n),这是它的最大优势。

📦 空间复杂度

  • O(n):需要额外的空间存储临时数组
  • 不是原地排序算法(in-place)

✅ 算法特性

  1. 稳定性:保持相等元素的原始顺序(关键:合并时left[i] <= right[j])
  2. 适用场景:大数据量排序、链表排序、外部排序
  3. 不适用场景:内存受限的小数据量排序

⚡ 优化技巧

  1. 小数组切换插入排序:当子数组长度较小时使用插入排序。

归并排序即使处理小规模数据仍需递归调用、分割合并等操作,其时间复杂度虽为O(n log n),但‌递归栈管理、函数调用等额外开销的常数因子较大‌,插入排序在小规模数据(如n≤15)时实际运行时间接近O(n)。数据量≤15时,元素局部有序概率高,实际操作次数接近线性,且其‌简单比较交换操作的常数因子极低‌,在阈值范围内性能显著优于归并排序。
ex: 常数因子指算法时间复杂度表示式中的固定系数(如O(2n)中的2),简单交换位移不需要递归方法调用也不需要分配空间,自然开销低。

private static final int INSERTION_THRESHOLD = 15;private static void mergeSort(int[] arr, int[] temp, int left, int right) {if (right - left <= INSERTION_THRESHOLD) {insertionSort(arr, left, right);return;}// 其余代码不变
}private static void insertionSort(int[] arr, int left, int right) {for (int i = left + 1; i <= right; i++) {int key = arr[i];int j = i - 1;while (j >= left && arr[j] > key) {arr[j + 1] = arr[j];j--;}arr[j + 1] = key;}
}
  1. 边界检查优化:在合并前检查左子数组最大值是否小于等于右子数组最小值。
// 在merge方法开始处添加
if (arr[mid] <= arr[mid + 1]) {return; // 已经有序,无需合并
}
  1. 避免重复复制:通过交换输入数组和临时数组的角色减少复制操作

💡 实际应用场景

  1. 大数据处理:MapReduce等分布式计算框架的底层排序
  2. 外部排序:处理超过内存容量的数据集
  3. 稳定排序需求:数据库排序、订单记录处理
  4. 链表排序:归并排序是链表最高效的排序方式(链表归并排序仅需修改节点指针指向,实现‌O(1)空间复杂度‌的原址排序)

🌐 总结

归并排序以其稳定高效的特性,成为处理大规模数据的首选算法之一。虽然需要额外空间,但它的分治思想在算法设计中具有重要地位,是理解递归和分治策略的经典案例。

学过Java的小伙伴,Arrays.sort()Collections.sort()‌底层就是使用优化后的归并排序呦,感兴趣的小伙伴可以再深入研究下。

http://www.xdnf.cn/news/981937.html

相关文章:

  • 实现 Spring Boot 3的组合注解,java
  • S2B2B农产品供应链交易多平台开发有哪些发展前景?如何维护?
  • docker 安装postgre并使用php进行连接
  • Spring MVC完全指南 - 从入门到精通
  • 华为交换机远程登录配置
  • 大语言模型的局限性与RAG基本框架和工作流实例
  • python数据结构和算法(4)
  • R语言缓释制剂QBD解决方案之三
  • 浅析hashmap
  • 7.7 Extracting and saving responses
  • C# 与低代码平台的融合:以活字格为例的 Web API 开发实践
  • 布尔字段命名陷阱:避免序列化错误的关键
  • pytorch 中前向传播和后向传播的自定义函数
  • vscode界面设置透明度--插件Glasslt-VSC
  • 【DETR目标检测】ISTD-DETR:一种基于DETR与超分辨率技术的红外小目标检测深度学习算法
  • 《HarmonyOSNext弹窗:ComponentContent动态玩转企业级弹窗》
  • 新闻类鸿蒙应用全链路测试实践:性能、兼容性与体验的深度优化
  • React Context 性能问题及解决方案深度解析
  • 【普及/提高−】P1025 ——[NOIP 2001 提高组] 数的划分
  • Cilium动手实验室: 精通之旅---23.Advanced Gateway API Use Cases
  • codeforces C. Devyatkino
  • Java并发工具包
  • 【59 Pandas+Pyecharts | 淘宝华为手机商品数据分析可视化】
  • 深度解读谷歌Brain++液态神经网络:重塑动态智能的流体计算革命
  • Gogs:一款极易搭建的自助 Git 服务
  • [Java恶补day22] 240. 搜索二维矩阵Ⅱ
  • React第六十节 Router中createHashRouter的具体使用详解及案例分析
  • android studio向左向右滑动页面
  • Babylon.js引擎
  • MMDG++:构筑多模态人脸防伪新防线,攻克伪造攻击与场景漂移挑战