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

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

文章目录

  • 前言
  • 🌟 核心思想
  • ⚙️ 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.pswp.cn/news/909450.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新零售视域下实体与虚拟店融合的技术逻辑与商业模式创新——基于开源AI智能名片与链动2+1模式的S2B2C生态构建

摘要&#xff1a;新零售的核心在于打破线上线下边界&#xff0c;构建“人、货、场”的全场景融合生态。本文提出&#xff0c;实体线下店与虚拟店的协同发展是新零售的重要演进方向&#xff0c;其底层逻辑在于满足消费者作为“现实人”的体验需求与“虚拟人”的效率需求。通过引…

可视化图解算法51:寻找第K大(数组中的第K个最大的元素)

牛客网 面试笔试 TOP101 | LeetCode 215. 数组中的第K个最大元素 1. 题目 描述 有一个整数数组&#xff0c;请你找出数组中第 k 大的数。 给定一个整数数组 a ,同时给定它的大小n和要找的 k &#xff0c;请返回第 k 大的数(包括重复的元素&#xff0c;不用去重)&…

DataWhale-零基础网络爬虫技术(一)

课程链接先给各位 ↓↓↓ &#xff08;点击即可食用.QAQ Datawhale-学用 AI,从此开始 一、引言 还是在笔记的开始&#xff0c;唠唠一些自己的故事 十年前第一次接触网络&#xff0c;也可以说是第一次接触计算机的时候&#xff0c;那时候还是在中学阶段&#xff0c;那时候大…

Linux02

目录 linux常用命令 用户和权限 压缩和解压缩 其他相关命令 Linux中安装常用软件 1.1. jdk的安装 1.1.1. 卸载linux中自带的open-jdk 1.1.2. 把安装包上传到 linux上 1.1.3. 解压安装包 1.1.4. 配置环境变量 1.1.5 验证环境变量 1.3 安装mysql 1.3.1. 检查依赖 1.…

JavaSE超详细笔记-网络编程篇-基于黑马

1. 什么是网络编程【理解】 1.1 概念 在网络通信协议下&#xff0c;不同计算机上运行的程序&#xff0c;进行的数据传输。 应用场景: 即时通信、网游对战、金融证券、国际贸易、邮件、等等。 不管是什么场景&#xff0c;都是计算机跟计算机之间通过网络进行数据传输Java中可以使…

时序数据库Influxdb3 core安装

本文介绍时序数据库Influxdb3 core(开源版本)的安装和简单使用以及调优参数的介绍。 预期&#xff1a; 安装时序数据库Influxdb3 core 创建数据库mydb 写入数据&#xff1b; 使用influxdb3-cli 和 grafana2种方式查询写入的数据 前期准备&#xff1a; linux服务器(本文服…

区间合并:区间合并问题

区间合并&#xff1a;区间合并问题 区间合并 www.acwing.com/problem/content/805/ 按区间的左端点排序 扫描整个区间&#xff0c;在这过程中把可能有交点的区间合并 全包含&#xff1a;不做改动相交&#xff1a;right 后移相离&#xff1a;更新至下一个维护区间 import j…

中国古代数学符号的演进 | 算筹 / 符号 / 算法

注&#xff1a;本文为“中国古代数学符号”相关合辑。 图片清晰度受引文原图所限。 略作重排&#xff0c;未整理去重。 如有内容异常&#xff0c;请看原文。 这个中国古代的数学瑰宝&#xff0c;到底厉害在哪&#xff1f; 原创 朱一文 科普中国 2024 年 07 月 31 日 15:30 北…

XMLDecoder、LDAP 注入与修复

问题&#xff1a;XMLDecoder注入 针对 xml 解码器的注入攻击 反序列化用户控制的 XML &#xff0c;程序没有进行验证&#xff0c; 会让攻击者有机会在服务器上执行恶意代 码。 例&#xff1a;下面代码片段中&#xff0c; XMLDecoder 处理不可信的输入。 ... XMLDecode…

Unity 对象层级处理小结

一.第一优先级Camera Culling Mask属性指定Camera显示的Layer,可以多选 Depth:Depth大的Camera显示的Layer显示在前面 二.避免使用PositionZ调整遮挡关系 在 2D 游戏中,虽然可以通过 Z 轴来调整显示顺序,但这与 2D 游戏的设计理念不符。在可以控制显示层级的多个要素或方…

python基础举例

最近又重新开始学python&#xff0c;浅浅记录下学习到的东西&#xff08;也方便自己回顾看&#xff09; 缩进、空格对于python很重要&#xff0c;一定要注意&#xff01; 以下代码是基于pycharm编写的。 01 输出 #注释 # 单行注释用# &#xff0c;ctrl/是单行注释的快捷键 # …

开疆智能ModbusTCP转Canopen网关连接汇川PLC配置案例

本案例是通过开疆智能研发的ModbusTCP转Canopen网关将汇川PLC与陀螺仪连接进行组网通讯。 准备阶段 软件&#xff1a;InoProShop(V1.7.3)&#xff0c;CANopen Configuration Studio PLC&#xff1a;汇川AC801-0221-R0R0 网关&#xff1a;开疆智能ModbusTCP转Canopen网关 陀…

Tess4J:基于 Java 的 OCR 解决方案

在现代软件开发中&#xff0c;图像识别与文本提取已成为许多应用场景中的关键环节。OCR&#xff08;Optical Character Recognition&#xff09; 技术使得从图像中提取文字成为可能。Tess4J 是一个基于 Java 的 OCR 开发库&#xff0c;它封装了 Google Tesseract OCR 引擎的本地…

Vue3 + JavaScript 父组件点击按钮触发子组件事件方法

在 Vue 3 中&#xff0c;父组件点击按钮触发子组件事件有以下三种常用方式&#xff1a; 方法 1&#xff1a;使用 ref 直接调用子组件方法&#xff08;推荐&#xff09; vue 复制 下载 <!-- 父组件 --> <template><button click"callChildMethod"…

超强人工智能解决方案套件InfiniSynapse:精准的业务理解、对各种数据源进行全模态联合智能分析--部署安装@Ubuntu22.04 @Docker

InfiniSynapse 通过自研的第二代LLM-Native RAG实现了企业业务的理解&#xff0c;精准的Schema召回保证数据的准确性。提供专门为大模型优化的InfiniSQL语言&#xff0c;从而可以更加准确的生成查询语句&#xff0c;通过 InfiniSQL 引擎让人类第一次对存储在各种数据源的全模态…

解决国内无法加载谷歌验证码(reCAPTCHA):URL 重定向配置指南

解决国内无法加载谷歌验证码&#xff08;reCAPTCHA&#xff09;&#xff1a;URL 重定向配置指南 在搭建网站或使用某些应用时&#xff0c;经常会遇到需要调用谷歌验证&#xff08;reCAPTCHA&#xff09;API 的情况。然而&#xff0c;由于网络环境的特殊性&#xff0c;国内多数…

【Qt】如何使用QtInstallerFramework打包Qt程序

使用 Qt Installer Framework 可以将你的 Qt 程序打包成一个带有安装向导的安装包&#xff0c;适用于 Windows、Linux 和 macOS 平台。以下是完整的打包流程&#xff0c;以你当前开发的 ecgexport 应用为例。 &#x1f9f0; 一、准备工作 1. 安装 Qt Installer Framework 下载…

如何编写高效的Prompt:从入门到精通

在人工智能时代&#xff0c;特别是随着大型语言模型(LLM)如ChatGPT、Claude等的普及&#xff0c;编写高质量的Prompt(提示词)已成为一项关键技能。一个好的Prompt可以显著提高AI输出的质量和相关性&#xff0c;而一个糟糕的Prompt可能导致无用甚至误导性的结果。本文将带你深入…

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…

【机械视觉】Halcon—【十三、实例找各个区域面积和中心点】

找区域面积和中心点 *获取图像 read_image (Image, fabrik) *关闭窗口 dev_close_window () *打开窗口 dev_open_window (0, 0, 512, 512, black, WindowID) *设置输出字体&#xff0c;14号字&#xff0c;Courier字体&#xff0c;粗体 set_display_font (WindowID, 14, mono, …