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

详解归并排序

归并排序

  • 归并排序的基本概念
  • 归并排序的详细步骤
      • 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. 合并阶段

合并的过程是归并排序的核心。合并阶段的目的是将每两个已排序的子数组合并成一个新的有序数组。合并时,需要依次比较两个子数组的元素,选择较小的元素放入最终的数组中,直到两个子数组的元素都被合并到结果数组中。

合并时的步骤如下:

  1. 合并 [27][43]

    • 比较 2743,先将较小的 27 放入合并数组中,接着将 43 放入合并数组中。
    • 结果:[27, 43]
  2. 合并 [3][9]

    • 比较 39,先将 3 放入合并数组中,接着将 9 放入合并数组中。
    • 结果:[3, 9]
  3. 合并 [82][10]

    • 比较 8210,先将较小的 10 放入合并数组中,接着将 82 放入合并数组中。
    • 结果:[10, 82]

经过这些合并后,我们得到如下数组:

[38] 和 [27, 43] 和 [3, 9] 和 [10, 82]

接下来,我们继续合并数组:

  • 合并 [38][27, 43]

    • 比较 3827,将 27 放入合并数组中,接着比较 3843,将 38 放入合并数组中,然后将 43 放入合并数组中。
    • 结果:[27, 38, 43]
  • 合并 [3, 9][10, 82]

    • 比较 310,将 3 放入合并数组中,接着比较 910,将 9 放入合并数组中,然后将 1082 依次放入合并数组中。
    • 结果:[3, 9, 10, 82]

最后,我们合并这两个数组:

  • 合并 [27, 38, 43][3, 9, 10, 82]
    • 比较 273,将 3 放入合并数组中,接着比较 279,将 9 放入合并数组中,再比较 2710,将 10 放入合并数组中,然后依次放入 27384382
    • 结果:[3, 9, 10, 27, 38, 43, 82]

到此为止,整个数组已经排好序了。

3. 归并排序的递归流程

归并排序的递归过程非常重要,以下是归并排序的基本逻辑:

  1. 分解:将数组不断拆分,直到每个子数组的长度为 1。
  2. 合并:将两个已排序的子数组合并成一个有序数组。

递归终止条件是数组的大小为 1,此时已经是有序的,我们就不需要进一步分解了。

时间复杂度分析

归并排序的时间复杂度主要由两个因素决定:

  1. 分解阶段:每一次递归将数组分成两半,因此分解的深度是 log₂ n
  2. 合并阶段:每次合并操作需要线性时间 O(n) ,即每个元素都需要被访问和合并一次。

因此,总的时间复杂度是 O(nlog₂ n) ,其中 n 是待排序数组的长度。这个时间复杂度在最坏、最好和平均情况下都是相同的。

空间复杂度分析

归并排序需要额外的空间来存储临时数组(用于合并操作)。每次合并操作需要使用一个大小为 O(n) 的临时数组,因此归并排序的空间复杂度是 O(n) ,这是它的一个缺点。

算法步骤

2-路归并排序

2-路归并排序将 R[low…high] 中的记录归并排序后放入T[low…high] 中。当序列长度等于1时,递归结束,否则:

  1. 将当前序列一分为二,求出分裂点 mid = ⌊(low + high) / 2⌋
  2. 对子序列 R[low…mid] 递归进行归并排序,结果放入 S[low…mid] 中;
  3. 对子序列 R[mid + 1…high] 递归进行归并排序,结果放入 S[mid + 1…high] 中;
  4. 调用算法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;     // 计算右子数组的大小
  • n1n2 分别是左子数组和右子数组的元素个数。
    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);
  • 打印排序后的数组。

归并排序的稳定性

归并排序是一种稳定的排序算法。稳定性意味着如果两个元素的值相同,排序后它们在原数组中的相对顺序不会改变。

归并排序的优缺点

优点:

  1. 时间复杂度稳定:归并排序的时间复杂度始终为 O(nlog₂ n) ,无论是最坏、平均还是最好情况都一样。
  2. 稳定性:它是一种 稳定 的排序算法,对于相等的元素,能保持它们的相对顺序。
  3. 适用于大数据集:归并排序在处理大数据集时非常有效,尤其是在外部排序中。

缺点:

  1. 空间复杂度较高:归并排序需要 O(n) 的额外空间,这比其他一些排序算法如快速排序要高。
  2. 不适用于小数据集:对于小规模数据,归并排序相比于插入排序、选择排序等简单的排序算法可能并不高效。
  3. 合并操作需要额外时间:每次合并操作需要处理所有元素,因此合并阶段的时间开销相对较大。

相关文章:

详解归并排序

归并排序 归并排序的基本概念归并排序的详细步骤1. 分解阶段2. 合并阶段3. 归并排序的递归流程 时间复杂度分析空间复杂度分析算法步骤2-路归并排序代码分析代码讲解1. 合并两个子数组的函数 merge()2. 归并排序函数 mergeSort()3. 打印数组的函数 printArray()4. 主函数 main(…...

45.在 Vue 3 中使用 OpenLayers 鼠标点击播放视频

引言 在 Web 开发中&#xff0c;地图可视化和互动功能是越来越重要的应用场景。OpenLayers 是一个强大的开源 JavaScript 库&#xff0c;用于显示和处理地图数据&#xff0c;支持多种地图服务和交互功能。在这个教程中&#xff0c;我们将介绍如何在 Vue 3 中集成 OpenLayers&a…...

《大话Java+playWright》系列教程初级篇-初识

后续代码会整理开源-大家期待吧&#xff01;&#xff01;&#xff01; 首先讲下为啥不用python&#xff0c;因为不想下载各种安装插件&#xff0c;太麻烦了&#xff0c;好多不兼容。 所以选择了java。 先来讲下什么是playwright&#xff0c;playwright是微软开源自动化测试工…...

05.HTTPS的实现原理-HTTPS的握手流程(TLS1.2)

05.HTTPS的实现原理-HTTPS的握手流程&#xff08;TLS1.2&#xff09; 简介1. TLS握手过程概述2. TLS握手过程细化3. 主密钥&#xff08;对称密钥&#xff09;生成过程4. 密码规范变更 简介 主要讲述了混合加密流程完成后&#xff0c;客户端和服务器如何共同获得相同的对称密钥…...

提示词工程

一、六何分析法快速写出准确的提示词 英文单词中文解释提问时的思考示例Why何故问题的背景&#xff0c;包括为什么做及目标&#xff08;做成什么样&#xff09;最近我们要与某品牌合作推广冲牙器&#xff0c;对方需要我们策划一场营销活动What何事具体是什么事写一个营销策划方…...

基于python网络爬虫的搜索引擎设计

一、毕业设计&#xff08;论文&#xff09;题目&#xff1a;基于网络爬虫的搜索引擎设计 - 基于网络爬虫的搜索引擎设计1 二、毕业设计&#xff08;论文&#xff09;工作自 2022-09-01 起至 2022-10-28 止 三、毕业设计&#xff08;论文&#xff09;内容要求&#xff1a; 主…...

ip-协议

文章目录 1. 网络层2. ip协议2.1 ip协议格式2.2 网段划分基本概念网段划分的两种方式为什么要网段划分&#xff1f;特殊的IP地址IP地址数量不足 2.3 私有IP与公网IP2.4 路由 3. IP的分片与组装为什么要分片与组装&#xff1f;如何分片&#xff1f;如何组装&#xff1f; 1. 网络…...

Git(11)之log显示支持中文

Git(11)之log显示支持中文 Author&#xff1a;Once Day Date&#xff1a;2024年12月21日 漫漫长路有人对你微笑过嘛… 参考文档&#xff1a;GIT使用log命令显示中文乱码_gitlab的log在matlab里显示中文乱码-CSDN博客 全系列文章可查看专栏: Git使用记录_Once_day的博客-CSD…...

oneflow深度学习框架使用问题总结(Windows/Linux)

目录 1.简述 2.在Windows下使用Oneflow深度学习框架&#xff08;错误记录&#xff0c;谨慎&#xff0c;官方不支持&#xff0c;需要WSL&#xff09; 2.1安装Anaconda 2.1创建虚拟环境 2.2安装Pytorch 2.3安装Pycharm 2.4 安装Oneflow 3.在Linux下使用Oneflow深度学习框…...

论文研读:AnimateDiff—通过微调SD,用图片生成动画

1.概述 AnimateDiff 设计了3个模块来微调通用的文生图Stable Diffusion预训练模型, 以较低的消耗实现图片到动画生成。 论文名&#xff1a;AnimateDiff: Animate Your Personalized Text-to-Image Diffusion Models without Specific Tuning 三大模块&#xff1a; 视频域适应…...

SQLAlchemy示例(连接数据库插入表数据)

背景需求 连接数据库&#xff0c;插入表中一些数据。 其用户是新建用户&#xff0c;所以只能插入&#xff0c;不能更新。 再次输入数据则使用更新数据语法&#xff0c;这个没调试。 #! /usr/bin/env python # -*- coding: utf-8 -*-from sqlalchemy import create_engine, …...

Springboot3国际化

国际化实现步骤 Spring Boot 3 提供了强大的国际化支持&#xff0c;使得应用程序可以根据用户的语言和区域偏好适配不同的语言和地区需求。 添加国际化资源文件&#xff1a; 国际化资源文件通常放在 src/main/resources 目录下&#xff0c;并按照不同的语言和地区命名&#xf…...

阿尔萨斯(JVisualVM)JVM监控工具

文章目录 前言阿尔萨斯(JVisualVM)JVM监控工具1. 阿尔萨斯的功能2. JVisualVM启动3. 使用 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff…...

框架专题:反射

1. 什么是反射&#xff1f; 简单来说&#xff0c;反射是一种程序自省的能力&#xff0c;即在程序运行时动态地获取其结构信息或操作其行为。这包括类、方法、属性等元信息。反射的核心在于让代码变得更加动态化&#xff0c;从而突破静态语言的限制。 以Java为例&#xff0c;反…...

【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&#xff1a;《Deliberative Alignment: Reasoning Enables Safer Language Models》翻译与解读 导读&#xff1a;2024年12月&#xff0c;这篇论文提出了一种名为“审慎式对齐 (Deliberative Alignment)”的新方法&#xff0c;旨在提高大型语言模型 (LLM) 的安全性。论…...

git设置项目远程仓库指向github的一个仓库

要将你的Git项目设置为指向GitHub上的远程仓库&#xff0c;你需要执行以下步骤&#xff1a; 创建GitHub仓库&#xff1a; 登录到你的GitHub账户。点击右上角的 “” 号&#xff0c;选择 “New repository” 创建一个新的仓库。填写仓库的名称&#xff0c;可以添加描述&#xff…...

实战演练JDK的模块化机制

实战演练JDK的模块化机制--楼兰 带你聊最纯粹的Java ​ 你发任你发,我用Java8。你用的JDK到什么版本了?很多开源框架都已经开始陆续升级JDK版本了。你对于JDK8往后陆陆续续更新的这些版本有什么感觉吗? ​ 很多人会说其实并没有太多的感觉。JDK的新版本不断推出一些不痛不痒…...

jdk17+springboot3项目加密部署

最近项目需要在第三方服务器部署&#xff0c;由于没有交付源码。所以需要将项目加密后再部署。 网上找了一圈&#xff0c;发现xjar这个开源项目&#xff0c;可以将代码加密后进行部署。看了下正是我需要的。 于是按照文档打包加密&#xff0c;但启动的时候居然报错。 这个结…...

rm -rf 删除/下bin lib lib64 sbin软链接系统恢复

背景 不小心删除了/bin、/lib、/lib64和/sbin这些目录的软链接&#xff0c;导致系统中的各种命令都无法正常使用。在尝试多种方法后&#xff0c;包括添加环境变量和使用绝对路径执行命令无法恢复&#xff0c;最终不重装完美解决。 [rootcentos-8 /]# ll 总用量 36 drwxr-xr-x …...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...

微服务通信安全:深入解析mTLS的原理与实践

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言&#xff1a;微服务时代的通信安全挑战 随着云原生和微服务架构的普及&#xff0c;服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...

Python实现简单音频数据压缩与解压算法

Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中&#xff0c;压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言&#xff0c;提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...