详解归并排序
归并排序
- 归并排序的基本概念
- 归并排序的详细步骤
- 1. 分解阶段
- 2. 合并阶段
- 3. 归并排序的递归流程
- 时间复杂度分析
- 空间复杂度分析
- 算法步骤
- 2-路归并排序
- 代码分析
- 代码讲解
- 1. 合并两个子数组的函数 `merge()`
- 2. 归并排序函数 `mergeSort()`
- 3. 打印数组的函数 `printArray()`
- 4. 主函数 `main()`
- 归并排序的稳定性
- 归并排序的优缺点
- 优点:
- 缺点:
归并排序的基本概念
归并排序(Merge Sort)是一种经典的分治算法。它将一个大问题分解成若干个小问题,递归地解决这些小问题,然后再合并成一个解决的大问题。归并排序的核心在于合并过程,即将两个已排序的子数组合并成一个有序的数组。
归并排序的详细步骤
1. 分解阶段
归并排序首先将数组分成两半,直到每个子数组中只有一个元素。对于每个子数组,显然它已经是“有序”的(因为一个元素是有序的)。这一步是递归进行的,直到数组被分割到最小的子数组。
举个例子,假设我们有一个数组:
[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]
此时每个子数组只有一个元素,递归的分解阶段就完成了。
2. 合并阶段
合并的过程是归并排序的核心。合并阶段的目的是将每两个已排序的子数组合并成一个新的有序数组。合并时,需要依次比较两个子数组的元素,选择较小的元素放入最终的数组中,直到两个子数组的元素都被合并到结果数组中。
合并时的步骤如下:
-
合并
[27]和[43]:- 比较
27和43,先将较小的27放入合并数组中,接着将43放入合并数组中。 - 结果:
[27, 43]
- 比较
-
合并
[3]和[9]:- 比较
3和9,先将3放入合并数组中,接着将9放入合并数组中。 - 结果:
[3, 9]
- 比较
-
合并
[82]和[10]:- 比较
82和10,先将较小的10放入合并数组中,接着将82放入合并数组中。 - 结果:
[10, 82]
- 比较
经过这些合并后,我们得到如下数组:
[38] 和 [27, 43] 和 [3, 9] 和 [10, 82]
接下来,我们继续合并数组:
-
合并
[38]和[27, 43]:- 比较
38和27,将27放入合并数组中,接着比较38和43,将38放入合并数组中,然后将43放入合并数组中。 - 结果:
[27, 38, 43]
- 比较
-
合并
[3, 9]和[10, 82]:- 比较
3和10,将3放入合并数组中,接着比较9和10,将9放入合并数组中,然后将10和82依次放入合并数组中。 - 结果:
[3, 9, 10, 82]
- 比较
最后,我们合并这两个数组:
- 合并
[27, 38, 43]和[3, 9, 10, 82]:- 比较
27和3,将3放入合并数组中,接着比较27和9,将9放入合并数组中,再比较27和10,将10放入合并数组中,然后依次放入27、38、43和82。 - 结果:
[3, 9, 10, 27, 38, 43, 82]
- 比较
到此为止,整个数组已经排好序了。
3. 归并排序的递归流程
归并排序的递归过程非常重要,以下是归并排序的基本逻辑:
- 分解:将数组不断拆分,直到每个子数组的长度为 1。
- 合并:将两个已排序的子数组合并成一个有序数组。
递归终止条件是数组的大小为 1,此时已经是有序的,我们就不需要进一步分解了。
时间复杂度分析
归并排序的时间复杂度主要由两个因素决定:
- 分解阶段:每一次递归将数组分成两半,因此分解的深度是 log₂ n 。
- 合并阶段:每次合并操作需要线性时间 O(n) ,即每个元素都需要被访问和合并一次。
因此,总的时间复杂度是 O(nlog₂ n) ,其中 n 是待排序数组的长度。这个时间复杂度在最坏、最好和平均情况下都是相同的。
空间复杂度分析
归并排序需要额外的空间来存储临时数组(用于合并操作)。每次合并操作需要使用一个大小为 O(n) 的临时数组,因此归并排序的空间复杂度是 O(n) ,这是它的一个缺点。
算法步骤
2-路归并排序
2-路归并排序将 R[low…high] 中的记录归并排序后放入T[low…high] 中。当序列长度等于1时,递归结束,否则:
- 将当前序列一分为二,求出分裂点 mid = ⌊(low + high) / 2⌋ ;
- 对子序列 R[low…mid] 递归进行归并排序,结果放入 S[low…mid] 中;
- 对子序列 R[mid + 1…high] 递归进行归并排序,结果放入 S[mid + 1…high] 中;
- 调用算法Merge,将有序的两个子序列 S[low…mid] 和 S[mid + 1…high] 归并为一个有序的序列 T[low…high] 。
代码分析
#include <stdio.h>// 合并两个子数组
void merge(int arr[], int left, int mid, int right) {int n1 = mid - left + 1; // 计算左子数组的大小int n2 = right - mid; // 计算右子数组的大小// 创建临时数组int leftArr[n1], rightArr[n2];// 将数据复制到临时数组for (int i = 0; i < n1; i++)leftArr[i] = arr[left + i];for (int j = 0; j < n2; j++)rightArr[j] = arr[mid + 1 + j];int i = 0, j = 0, k = left;// 合并两个临时数组到原数组while (i < n1 && j < n2) {if (leftArr[i] <= rightArr[j]) {arr[k] = leftArr[i];i++;} else {arr[k] = rightArr[j];j++;}k++;}// 将剩余的元素拷贝到原数组while (i < n1) {arr[k] = leftArr[i];i++;k++;}while (j < n2) {arr[k] = rightArr[j];j++;k++;}
}// 递归进行归并排序
void mergeSort(int arr[], int left, int right) {if (left < right) {// 计算中间点int mid = left + (right - left) / 2;// 递归排序左半部分mergeSort(arr, left, mid);// 递归排序右半部分mergeSort(arr, mid + 1, right);// 合并已排序的部分merge(arr, left, mid, right);}
}// 打印数组的函数
void printArray(int arr[], int size) {for (int i = 0; i < size; i++)printf("%d ", arr[i]);printf("\n");
}int main() {int arr[] = {38, 27, 43, 3, 9, 82, 10};int n = sizeof(arr) / sizeof(arr[0]);printf("排序前的数组:\n");printArray(arr, n);mergeSort(arr, 0, n - 1); // 调用归并排序printf("排序后的数组:\n");printArray(arr, n);return 0;
}
代码讲解
1. 合并两个子数组的函数 merge()
void merge(int arr[], int left, int mid, int right) {
merge()函数的目的是将两个已经排好序的子数组合并成一个有序的数组。arr[]是待排序的数组,left是左子数组的起始索引,mid是中间索引,right是右子数组的结束索引。
int n1 = mid - left + 1; // 计算左子数组的大小int n2 = right - mid; // 计算右子数组的大小
n1和n2分别是左子数组和右子数组的元素个数。
int leftArr[n1], rightArr[n2];
leftArr[]和rightArr[]是临时数组,用于存储左子数组和右子数组的元素。
for (int i = 0; i < n1; i++)leftArr[i] = arr[left + i];for (int j = 0; j < n2; j++)rightArr[j] = arr[mid + 1 + j];
- 将
arr[]数组中的数据分别复制到leftArr[]和rightArr[]中。
int i = 0, j = 0, k = left;
i用于遍历leftArr[],j用于遍历rightArr[],k用于遍历原始数组arr[]。
while (i < n1 && j < n2) {if (leftArr[i] <= rightArr[j]) {arr[k] = leftArr[i];i++;} else {arr[k] = rightArr[j];j++;}k++;}
- 合并
leftArr[]和rightArr[]的元素到arr[]中:- 比较
leftArr[i]和rightArr[j],将较小的元素放入arr[k]中。 - 如果左子数组的元素小或相等,就将左子数组的元素放入
arr[k],否则将右子数组的元素放入。
- 比较
while (i < n1) {arr[k] = leftArr[i];i++;k++;}while (j < n2) {arr[k] = rightArr[j];j++;k++;}
- 如果左子数组或右子数组中还有剩余元素,继续将它们放入
arr[]中。
2. 归并排序函数 mergeSort()
void mergeSort(int arr[], int left, int right) {
mergeSort()函数负责将数组不断分割并排序。它的作用是将数组拆分成越来越小的子数组,直到每个子数组只有一个元素,然后调用merge()合并已排序的子数组。
if (left < right) {
- 递归终止条件:如果左边索引小于右边索引,说明子数组包含多于一个元素,继续分割和排序。
int mid = left + (right - left) / 2; // 计算中间点
- 计算中间点
mid,将数组分割成左右两部分。
mergeSort(arr, left, mid); // 递归排序左半部分mergeSort(arr, mid + 1, right); // 递归排序右半部分
- 递归调用
mergeSort()来分别排序左半部分和右半部分。
merge(arr, left, mid, right); // 合并已排序的部分
- 合并左右两部分,最终形成一个有序数组。
3. 打印数组的函数 printArray()
void printArray(int arr[], int size) {for (int i = 0; i < size; i++)printf("%d ", arr[i]);printf("\n");
}
printArray()用于打印数组中的元素。size是数组的长度,arr[]是待打印的数组。
4. 主函数 main()
int main() {int arr[] = {38, 27, 43, 3, 9, 82, 10};int n = sizeof(arr) / sizeof(arr[0]);
- 定义一个待排序的数组
arr[],并通过sizeof(arr) / sizeof(arr[0])计算数组的长度n。
printf("排序前的数组:\n");printArray(arr, n);
- 打印排序前的数组。
mergeSort(arr, 0, n - 1); // 调用归并排序
- 调用
mergeSort()函数进行排序。
printf("排序后的数组:\n");printArray(arr, n);
- 打印排序后的数组。
归并排序的稳定性
归并排序是一种稳定的排序算法。稳定性意味着如果两个元素的值相同,排序后它们在原数组中的相对顺序不会改变。
归并排序的优缺点
优点:
- 时间复杂度稳定:归并排序的时间复杂度始终为 O(nlog₂ n) ,无论是最坏、平均还是最好情况都一样。
- 稳定性:它是一种 稳定 的排序算法,对于相等的元素,能保持它们的相对顺序。
- 适用于大数据集:归并排序在处理大数据集时非常有效,尤其是在外部排序中。
缺点:
- 空间复杂度较高:归并排序需要 O(n) 的额外空间,这比其他一些排序算法如快速排序要高。
- 不适用于小数据集:对于小规模数据,归并排序相比于插入排序、选择排序等简单的排序算法可能并不高效。
- 合并操作需要额外时间:每次合并操作需要处理所有元素,因此合并阶段的时间开销相对较大。
相关文章:
详解归并排序
归并排序 归并排序的基本概念归并排序的详细步骤1. 分解阶段2. 合并阶段3. 归并排序的递归流程 时间复杂度分析空间复杂度分析算法步骤2-路归并排序代码分析代码讲解1. 合并两个子数组的函数 merge()2. 归并排序函数 mergeSort()3. 打印数组的函数 printArray()4. 主函数 main(…...
45.在 Vue 3 中使用 OpenLayers 鼠标点击播放视频
引言 在 Web 开发中,地图可视化和互动功能是越来越重要的应用场景。OpenLayers 是一个强大的开源 JavaScript 库,用于显示和处理地图数据,支持多种地图服务和交互功能。在这个教程中,我们将介绍如何在 Vue 3 中集成 OpenLayers&a…...
《大话Java+playWright》系列教程初级篇-初识
后续代码会整理开源-大家期待吧!!! 首先讲下为啥不用python,因为不想下载各种安装插件,太麻烦了,好多不兼容。 所以选择了java。 先来讲下什么是playwright,playwright是微软开源自动化测试工…...
05.HTTPS的实现原理-HTTPS的握手流程(TLS1.2)
05.HTTPS的实现原理-HTTPS的握手流程(TLS1.2) 简介1. TLS握手过程概述2. TLS握手过程细化3. 主密钥(对称密钥)生成过程4. 密码规范变更 简介 主要讲述了混合加密流程完成后,客户端和服务器如何共同获得相同的对称密钥…...
提示词工程
一、六何分析法快速写出准确的提示词 英文单词中文解释提问时的思考示例Why何故问题的背景,包括为什么做及目标(做成什么样)最近我们要与某品牌合作推广冲牙器,对方需要我们策划一场营销活动What何事具体是什么事写一个营销策划方…...
基于python网络爬虫的搜索引擎设计
一、毕业设计(论文)题目:基于网络爬虫的搜索引擎设计 - 基于网络爬虫的搜索引擎设计1 二、毕业设计(论文)工作自 2022-09-01 起至 2022-10-28 止 三、毕业设计(论文)内容要求: 主…...
ip-协议
文章目录 1. 网络层2. ip协议2.1 ip协议格式2.2 网段划分基本概念网段划分的两种方式为什么要网段划分?特殊的IP地址IP地址数量不足 2.3 私有IP与公网IP2.4 路由 3. IP的分片与组装为什么要分片与组装?如何分片?如何组装? 1. 网络…...
Git(11)之log显示支持中文
Git(11)之log显示支持中文 Author:Once Day Date:2024年12月21日 漫漫长路有人对你微笑过嘛… 参考文档:GIT使用log命令显示中文乱码_gitlab的log在matlab里显示中文乱码-CSDN博客 全系列文章可查看专栏: Git使用记录_Once_day的博客-CSD…...
oneflow深度学习框架使用问题总结(Windows/Linux)
目录 1.简述 2.在Windows下使用Oneflow深度学习框架(错误记录,谨慎,官方不支持,需要WSL) 2.1安装Anaconda 2.1创建虚拟环境 2.2安装Pytorch 2.3安装Pycharm 2.4 安装Oneflow 3.在Linux下使用Oneflow深度学习框…...
论文研读:AnimateDiff—通过微调SD,用图片生成动画
1.概述 AnimateDiff 设计了3个模块来微调通用的文生图Stable Diffusion预训练模型, 以较低的消耗实现图片到动画生成。 论文名:AnimateDiff: Animate Your Personalized Text-to-Image Diffusion Models without Specific Tuning 三大模块: 视频域适应…...
SQLAlchemy示例(连接数据库插入表数据)
背景需求 连接数据库,插入表中一些数据。 其用户是新建用户,所以只能插入,不能更新。 再次输入数据则使用更新数据语法,这个没调试。 #! /usr/bin/env python # -*- coding: utf-8 -*-from sqlalchemy import create_engine, …...
Springboot3国际化
国际化实现步骤 Spring Boot 3 提供了强大的国际化支持,使得应用程序可以根据用户的语言和区域偏好适配不同的语言和地区需求。 添加国际化资源文件: 国际化资源文件通常放在 src/main/resources 目录下,并按照不同的语言和地区命名…...
阿尔萨斯(JVisualVM)JVM监控工具
文章目录 前言阿尔萨斯(JVisualVM)JVM监控工具1. 阿尔萨斯的功能2. JVisualVM启动3. 使用 前言 如果您觉得有用的话,记得给博主点个赞,评论,收藏一键三连啊,写作不易啊^ _ ^。 而且听说点赞的人每天的运气都不会太差ÿ…...
框架专题:反射
1. 什么是反射? 简单来说,反射是一种程序自省的能力,即在程序运行时动态地获取其结构信息或操作其行为。这包括类、方法、属性等元信息。反射的核心在于让代码变得更加动态化,从而突破静态语言的限制。 以Java为例,反…...
【Go】context标准库
文章目录 1. 概述1.1 什么是 Context1.2 设计原理1.3 使用场景源码分析核心:Context接口4个实现6个方法TODO 和 BackgroundWithCancelcancelpropagateCancel 绑定父对象WithTimeout 和 WithDeadlineWithValue总结参考1. 概述 基于版本: go1.22.3/src/context/context.go 1.1…...
LLMs之o3:《Deliberative Alignment: Reasoning Enables Safer Language Models》翻译与解读
LLMs之o3:《Deliberative Alignment: Reasoning Enables Safer Language Models》翻译与解读 导读:2024年12月,这篇论文提出了一种名为“审慎式对齐 (Deliberative Alignment)”的新方法,旨在提高大型语言模型 (LLM) 的安全性。论…...
git设置项目远程仓库指向github的一个仓库
要将你的Git项目设置为指向GitHub上的远程仓库,你需要执行以下步骤: 创建GitHub仓库: 登录到你的GitHub账户。点击右上角的 “” 号,选择 “New repository” 创建一个新的仓库。填写仓库的名称,可以添加描述ÿ…...
实战演练JDK的模块化机制
实战演练JDK的模块化机制--楼兰 带你聊最纯粹的Java 你发任你发,我用Java8。你用的JDK到什么版本了?很多开源框架都已经开始陆续升级JDK版本了。你对于JDK8往后陆陆续续更新的这些版本有什么感觉吗? 很多人会说其实并没有太多的感觉。JDK的新版本不断推出一些不痛不痒…...
jdk17+springboot3项目加密部署
最近项目需要在第三方服务器部署,由于没有交付源码。所以需要将项目加密后再部署。 网上找了一圈,发现xjar这个开源项目,可以将代码加密后进行部署。看了下正是我需要的。 于是按照文档打包加密,但启动的时候居然报错。 这个结…...
rm -rf 删除/下bin lib lib64 sbin软链接系统恢复
背景 不小心删除了/bin、/lib、/lib64和/sbin这些目录的软链接,导致系统中的各种命令都无法正常使用。在尝试多种方法后,包括添加环境变量和使用绝对路径执行命令无法恢复,最终不重装完美解决。 [rootcentos-8 /]# ll 总用量 36 drwxr-xr-x …...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
