归并排序是一种基于分治策略的经典排序算法,由约翰・冯・诺依曼在 1945 年提出。它以稳定的 O (nlogn) 时间复杂度和良好的可并行性,在大规模数据排序场景中占据重要地位。与快速排序的 “先分区后排序” 不同,归并排序采用 “先排序后合并” 的思路,其稳定性(相等元素相对顺序不变)使其在数据库排序等对稳定性有要求的场景中备受青睐。

归并排序算法核心思路

归并排序的核心思想是分治(Divide and Conquer),即将一个大问题分解为若干个小问题,分别解决后再将结果合并。具体可分为三个步骤:分解(Divide)、治理(Conquer)、合并(Merge)。

关键步骤解析

(1)分解(Divide)

将待排序数组不断二分,直到每个子数组只包含一个元素(此时子数组天然有序)。例如,对数组[8, 4, 5, 7, 1, 3, 6, 2],分解过程如下:

  • 第一层:[8,4,5,7] 和 [1,3,6,2]
  • 第二层:[8,4]、[5,7] 和 [1,3]、[6,2]
  • 第三层:[8]、[4]、[5]、[7]、[1]、[3]、[6]、[2]
(2)治理(Conquer)

递归处理每个子数组,由于当子数组长度为 1 时已天然有序,因此这一步主要是递归的终止条件。

(3)合并(Merge)

将两个有序子数组合并为一个更大的有序数组,这是归并排序的核心步骤。合并过程需借助一个辅助数组,具体步骤如下:

  1. 初始化两个指针i和j,分别指向两个子数组的起始位置。
  1. 初始化辅助数组指针k,指向辅助数组起始位置。
  1. 比较i和j指向的元素,将较小的元素放入辅助数组,并移动对应指针。
  1. 重复步骤 3,直到其中一个子数组遍历完毕。
  1. 将剩余子数组的元素依次放入辅助数组。
  1. 将辅助数组的元素复制回原数组的对应位置。

归并排序的整体流程

归并排序的整体流程是 “分解 - 合并” 的递归过程,可用以下流程图表示:

 归并排序的 Java 实现(基础版)

public class MergeSortBasic {// 对外暴露的排序方法public static void sort(int[] arr) {if (arr == null || arr.length <= 1) {return;}int[] temp = new int[arr.length]; // 辅助数组(避免递归中重复创建)mergeSort(arr, 0, arr.length - 1, temp);}// 递归执行归并排序private static void mergeSort(int[] arr, int left, int right, int[] temp) {if (left < right) {int mid = left + (right - left) / 2; // 避免溢出,等价于(left + right) / 2mergeSort(arr, left, mid, temp); // 左子数组排序mergeSort(arr, mid + 1, right, temp); // 右子数组排序merge(arr, left, mid, right, temp); // 合并}}// 合并两个有序子数组private static void merge(int[] arr, int left, int mid, int right, int[] temp) {int i = left; // 左子数组起始索引int j = mid + 1; // 右子数组起始索引int k = left; // 辅助数组起始索引// 比较并合并两个子数组while (i <= mid && j <= right) {if (arr[i] <= arr[j]) { // 保持稳定性(<= 确保相等元素左子数组在前)temp[k++] = arr[i++];} else {temp[k++] = arr[j++];}}// 处理左子数组剩余元素while (i <= mid) {temp[k++] = arr[i++];}// 处理右子数组剩余元素while (j <= right) {temp[k++] = arr[j++];}// 将辅助数组复制回原数组for (k = left; k <= right; k++) {arr[k] = temp[k];}}// 测试public static void main(String[] args) {int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};sort(arr);for (int num : arr) {System.out.print(num + " "); // 输出:1 2 3 4 5 6 7 8}}}

LeetCode 例题实战

例题 1:912. 排序数组(中等)

题目描述:给你一个整数数组 nums,请你将该数组升序排列。

示例

输入:nums = [5,2,3,1]

输出:[1,2,3,5]

解题思路:直接使用归并排序对数组进行排序。与快速排序相比,归并排序的时间复杂度稳定为 O (nlogn),适合处理包含大量重复元素或近乎有序的数组。

Java 代码实现


class Solution {public int[] sortArray(int[] nums) {if (nums == null || nums.length <= 1) {return nums;}int[] temp = new int[nums.length];mergeSort(nums, 0, nums.length - 1, temp);return nums;}private void mergeSort(int[] arr, int left, int right, int[] temp) {if (left < right) {int mid = left + (right - left) / 2;mergeSort(arr, left, mid, temp);mergeSort(arr, mid + 1, right, temp);merge(arr, left, mid, right, temp);}}private void merge(int[] arr, int left, int mid, int right, int[] temp) {int i = left;int j = mid + 1;int k = left;while (i <= mid && j <= right) {if (arr[i] <= arr[j]) {temp[k++] = arr[i++];} else {temp[k++] = arr[j++];}}while (i <= mid) {temp[k++] = arr[i++];}while (j <= right) {temp[k++] = arr[j++];}for (k = left; k <= right; k++) {arr[k] = temp[k];}}}

复杂度分析

  • 时间复杂度:O (nlogn),无论数组初始状态如何,分解和合并的总操作次数均为 O (nlogn)。
  • 空间复杂度:O (n),来自辅助数组temp。

例题 2:315. 计算右侧小于当前元素的个数(困难)

题目描述:给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质:counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

示例

输入:nums = [5,2,6,1]

输出:[2,1,1,0]

解释:

5 的右侧有 2 个更小的元素 (2 和 1)

2 的右侧有 1 个更小的元素 (1)

6 的右侧有 1 个更小的元素 (1)

1 的右侧没有更小的元素

解题思路:本题可借助归并排序的合并过程统计逆序对。在合并两个有序子数组时,当右子数组元素nums[j]小于左子数组元素nums[i]时,右子数组中j到mid+1的所有元素均小于nums[i],因此counts[i]需加上right - j + 1(剩余元素个数)。为避免原数组索引被排序打乱,需额外记录元素原始索引。

Java 代码实现

class Solution {private int[] counts;public List<Integer> countSmaller(int[] nums) {int n = nums.length;counts = new int[n];if (n == 0) {return new ArrayList<>();}// 构建数组,存储值和原始索引int[][] arr = new int[n][2];for (int i = 0; i < n; i++) {arr[i][0] = nums[i];arr[i][1] = i;}int[][] temp = new int[n][2]; // 辅助数组mergeSort(arr, 0, n - 1, temp);// 转换为List返回List<Integer> result = new ArrayList<>();for (int count : counts) {result.add(count);}return result;}private void mergeSort(int[][] arr, int left, int right, int[][] temp) {if (left < right) {int mid = left + (right - left) / 2;mergeSort(arr, left, mid, temp);mergeSort(arr, mid + 1, right, temp);merge(arr, left, mid, right, temp);}}private void merge(int[][] arr, int left, int mid, int right, int[][] temp) {int i = left;int j = mid + 1;int k = left;while (i <= mid && j <= right) {if (arr[i][0] <= arr[j][0]) {// 右子数组中j到right的元素均小于arr[i][0]counts[arr[i][1]] += right - j + 1;temp[k++] = arr[i++];} else {temp[k++] = arr[j++];}}// 左子数组剩余元素,右侧已无元素,无需计数while (i <= mid) {temp[k++] = arr[i++];}// 右子数组剩余元素,左侧已无元素,无需计数while (j <= right) {temp[k++] = arr[j++];}// 复制回原数组for (k = left; k <= right; k++) {arr[k] = temp[k];}}}

复杂度分析

  • 时间复杂度:O (nlogn),归并排序的时间复杂度为 O (nlogn),合并过程中计数操作仅为 O (1)。
  • 空间复杂度:O (n),来自辅助数组和存储索引的数组。

考研 408 例题解析

例题 1:基本概念与时间复杂度分析(选择题)

题目:下列关于归并排序的叙述中,正确的是( )。

A. 归并排序的时间复杂度为 O (nlogn),空间复杂度为 O (1)

B. 归并排序是不稳定的排序算法

C. 归并排序在最坏情况下的时间复杂度优于快速排序

D. 归并排序适合对链表进行排序

答案:C、D

解析

  • A 错误:归并排序空间复杂度为 O (n)(辅助数组),链表排序可优化至 O (1),但数组排序仍为 O (n)。
  • B 错误:归并排序是稳定排序(合并时相等元素按原顺序放置)。
  • C 正确:归并排序最坏时间复杂度为 O (nlogn),而快速排序最坏为 O (n²)。
  • D 正确:链表归并排序无需辅助数组,通过指针操作合并,空间复杂度 O (logn)(递归栈),效率高。

例题 2:算法设计题(408 高频考点)

题目:已知单链表的头指针为head,设计一个算法对该链表进行归并排序,要求时间复杂度为 O (nlogn),空间复杂度为 O (logn)。

解题思路:链表归并排序与数组归并排序思路一致,但需注意:

  1. 分解:通过快慢指针找到链表中点,断开链表为左右两部分。
  1. 合并:通过指针操作合并两个有序链表,无需辅助数组。

Java 代码实现

class ListNode {int val;ListNode next;ListNode(int x) {val = x;next = null;}}public class MergeSortList {public ListNode sortList(ListNode head) {// 基线条件:空链表或单节点链表无需排序if (head == null || head.next == null) {return head;}// 找到链表中点并断开ListNode mid = findMid(head);ListNode rightHead = mid.next;mid.next = null; // 断开// 递归排序左右链表ListNode left = sortList(head);ListNode right = sortList(rightHead);// 合并有序链表return merge(left, right);}// 快慢指针找中点(偶数节点取左中点)private ListNode findMid(ListNode head) {if (head == null) {return null;}ListNode slow = head;ListNode fast = head.next; // 确保偶数时取左中点while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;}return slow;}// 合并两个有序链表private ListNode merge(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode curr = dummy;while (l1 != null < / doubaocanvas >
}

希望本文能够帮助读者更深入地理解归并排序,并在实际项目中发挥其优势。谢谢阅读!


希望这份博客能够帮助到你。如果有其他需要修改或添加的地方,请随时告诉我。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/88646.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/88646.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/88646.shtml

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

相关文章

Kotlin基础学习记录

变量和函数 变量 // val为常量&#xff0c;一旦赋值就不可变 val a 10 val a: Int 10 a 3 // 报错// var为变量 var a 10 a 3 var b: Int 20 b 2函数fun add(a: Int, b: Int): Unit {a b // 报错&#xff0c;参数默认val }fun add(a: Int, b: Int) {var x: Int ax b …

【C#】GraphicsPath的用法

在 C# 中&#xff0c;GraphicsPath 是 GDI 提供的一个非常强大的类&#xff0c;用于创建和操作复杂图形路径。它可以用来绘制直线、曲线、多边形等形状&#xff0c;并支持判断点是否在路径内或路径的轮廓上。一、基本概念GraphicsPath 类功能&#xff1a;添加各种几何图形&…

C语言32个关键字

文章目录数据类型1、数据类型&#xff08;12个&#xff09;控制语句2、控制语句关键字&#xff08;12个&#xff09;存储类型3、存储类型关键字&#xff08;4个&#xff09;其他关键字4、其他关键字&#xff08;4个&#xff09;​一共32个关键字分为 数据类型 1、数据类型&am…

粒子滤波|粒子滤波的相关算法理论介绍

在自动控制、导航、目标跟踪等众多领域&#xff0c;系统状态估计是获取真实状态的关键环节。由于观测信号常受噪声干扰&#xff0c;滤波技术成为提取可靠信息的核心手段。本文将围绕目标跟踪技术中的滤波算法理论展开&#xff0c;重点解析粒子滤波框架的原理与应用。一、动态系…

Jenkins+Gitee+Docker容器化部署

写在前文 本文主要是通过Jenkins的maven项目版本GiteeDocker-maven插件来进行部署的&#xff0c;本文没有使用dockerfile/docker-compose。 本文默认已经安装了Docker 1、安装Jenkins Step1、创建文件夹当作映射jenkins的home文件夹 mkdir /app/jenkins Step2、赋权&#xff…

[Meetily后端框架] 多模型-Pydantic AI 代理-统一抽象 | SQLite管理

第5章&#xff1a;人工智能模型交互&#xff08;Pydantic-AI 代理&#xff09; 欢迎回来&#xff01; 在上一章第四章&#xff1a;文字记录处理逻辑中&#xff0c;我们学习了TranscriptProcessor如何将冗长的会议记录分解为称为"块"的较小片段&#xff0c;因为人工…

利用DeepSeek实现rust调用duckdb动态链接库的duckdb CLI

提示词&#xff1a;请用rust调用duckdb-rs实现一个duckdb CLI,支持语法突出显示和计时&#xff0c;还支持命令行管道输入输出 Cargo.toml [package] name "duckdb-cli" version "0.1.0" edition "2024"[dependencies] duckdb "1.3.1&qu…

C++,从汇编角度看《虚拟继承的邪恶》

刷到一篇文章&#xff1a; 作者&#xff1a; 原文&#xff1a;虛擬繼承的邪惡 讨论到这样的一个程序&#xff0c;最终输出什么&#xff1f;&#xff1f;&#xff1f; 代码有简化命名 using namespace std;class A { public:A(int a 0) : v(a) {};int v; };template <type…

多 Agent 强化学习实践指南(一):CTDE PPO 在合作捕食者-猎物游戏中的应用详解

我们来详细讲解如何在合作捕食者-猎物游戏中结合 PPO (Proximal Policy Optimization) 算法。我们将聚焦于 CTDE&#xff08;Centralized Training, Decentralized Execution&#xff0c;集中训练、分散执行&#xff09; 模式&#xff0c;因为这是处理合作多 Agent 任务的常用且…

Web应用文件上传安全设计指南

引言 在当今的Web应用中&#xff0c;文件上传功能已成为基础且必要的服务能力&#xff0c;但不当的设计可能带来目录遍历、代码注入、服务端资源耗尽等安全风险。本文从威胁模型、安全设计原则、技术实现三个维度&#xff0c;系统阐述安全文件上传架构的设计要点。 一、威胁模型…

用 React Three Fiber 实现 3D 城市模型的扩散光圈特效

本文介绍了如何使用 React Three Fiber&#xff08;R3F&#xff09;和 Three.js 实现一个从中心向外扩散的光圈特效&#xff08;DiffuseAperture 组件&#xff09;&#xff0c;并将其集成到城市 3D 模型&#xff08;CityModel 组件&#xff09;中。该特效通过动态调整圆柱几何体…

【牛客刷题】COUNT数字计数

文章目录 一、题目介绍二、题解思路三、算法实现四、复杂度分析五 、关键步骤解析5.1 数字分解5.2 三种情况处理5.2.1 情况1: d < c u r d < cur d<cur(完整周期)5.2.2 情况2: d = c u r d = cur d=cur(混合周期)5.2.3 情况3: d > c u r d > cur d>cu…

AGV穿梭不“迷路”CCLinkIE转Modbus TCP的衔接技巧

在AGV控制系统集成中&#xff0c;工程师常面临一个现实难题&#xff1a;如何让CCLinkIE总线与Modbus TCP设备实现高效通信&#xff1f;这种跨协议的连接需求&#xff0c;往往需要耗费大量时间调试。本文将通过实际案例解析&#xff0c;为制造行业工程师提供可复用的解决方案。【…

【代码随想录】刷题笔记——哈希表篇

目录 242. 有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和 454. 四数相加 II 383. 赎金信 15. 三数之和 18. 四数之和 242. 有效的字母异位词 思路 代码 class Solution {public boolean isAnagram(String s, String t) {if (s.length() ! t.length()…

Python爬虫实战:研究messytables库相关技术

1. 引言 在当今数字化时代,互联网上存在着大量有价值的数据。然而,这些数据通常以不规则的格式存在,尤其是表格数据,可能包含复杂的表头、合并单元格、不规则布局等问题。传统的数据处理工具往往难以应对这些挑战。 网络爬虫技术可以帮助我们从网页上自动提取数据,而 mes…

Vue3的组件通信方式

通信方式适用层级数据流向复杂度Props/Emits父子组件单向/双向★☆☆v-model父子组件双向★☆☆Provide/Inject跨层级组件自上而下★★☆事件总线任意组件任意方向★★★Pinia/Vuex全局状态任意方向★★☆Refs模板引用父子组件父→子★☆☆作用域插槽父子组件子→父★★☆Web W…

创客匠人:大健康创始人IP如何用“社会责任”构建品牌护城河

一、商业与责任的失衡困局部分大健康IP将利润置于首位&#xff0c;甚至牺牲用户利益&#xff0c;导致品牌形象脆弱。某保健品公司因夸大宣传被曝光后&#xff0c;尽管销量曾达千万&#xff0c;却因缺乏社会认同&#xff0c;一夜之间崩塌&#xff0c;证明没有社会责任支撑的商业…

AI:机器人未来的形态是什么?

机器人未来的形态将受到技术进步、应用场景需求和社会接受度的综合影响&#xff0c;以下是对未来机器人形态的预测&#xff0c;涵盖技术趋势、设计方向和应用场景&#xff1a; 1. 形态多样化与通用化 人形机器人&#xff08;Humanoid Robots&#xff09;&#xff1a; 趋势&…

创建 UIKit 项目教程

一、打开 XCode&#xff0c;选择 iOS 下的 App&#xff0c;然后点 Next二、Interface 选择 Storyboard&#xff0c;然后点 Next三、删掉 Main.storyboard四、删掉 SceneDelegate.swift五、AppDelegate.swift 只保留第一个函数六、在 AppDelegate.swift 文件里的 application 函…

防爬虫君子协定 Robots.txt 文件

1.什么是robots.txt ? robots.txt是一个位于网站根目录的文本文件,用于指导搜索引擎爬虫如何访问和抓取网站内容。它遵循特定的语法规则,是网站与爬虫通信的重要工具。当搜索引擎访问一个网站时,它首先会检查该网站的根域下是否有一个叫做robots.txt的纯文本文件。Robots.…