【有营养的算法笔记】归并排序

news2024/11/16 21:50:19

👑作者主页:@进击的安度因
🏠学习社区:进击的安度因(个人社区)
📖专栏链接:有营养的算法笔记

文章目录

  • 一、思路
  • 二、模板讲解
  • 三、模板测试
  • 四、加练 —— 逆序对的数量

今天讲解的内容是 归并排序模板及其练习题 ~

一、思路

归并排序,从它的名字我们可以大约猜测这个排序的步骤。归 —— 归置,意思是整理收拾,归置原位;并 —— 合并,将序列合并回去,而归并排序的主题思路也差不多就是这样。

归并排序的思想是 分治,就是递归。归并和 上篇笔记的快排 算是 分治 中的两个难点,我们学习初级算法,归并部分基本只需要吃透这两部分就 ok 。

接下来我们梳理一下 归并排序 的主要步骤:

image-20221207202211147

  1. 确定分界点,分界点一般为中点:mid = q[l + r >> 1]
  2. 递归排序左右区间,使区间有序
  3. 双指针合并区间

二、模板讲解

前面我们讲了主要步骤,我们再挖一下每一步该干什么,再给出模板:

第一点确定分界点没什么好说的,就是确定 每次归并排序划分区间的分界点

第二点的话,就是递归左右区间的问题,而递归排序之前就只是 确定分界点 而已,说明是会先 递归到最底层,然后逐渐排序,归并返回的

第三点的话,这一步就得好好说说:

双指针合并区间,说着容易,但是其实不是那么好实现的。

如果不借助额外空间,那么合并时,就可能会造成数据覆盖等错误情况。

所以需要借助 辅助数组 tmp排序过程中,将区间内元素有序放置于 tmp 中,当 tmp 数组对于每次归并的区间有序后,将数据倒回原数组

梳理一遍后,我们再看 模板

void merge_sort(int q[], int l, int r)
{
    if (l >= r)
        return; 
    // 1. 确定分界点
    int mid = l + r >> 1;   
    // 2. 递归排序左右区间
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);
    // 3. 双指针合并区间
    int i = l, j = mid + 1, k = 0;
    while (i <= mid && j <= r)
    {
        // 写法具有稳定性
        if (q[i] <= q[j])
            tmp[k++] = q[i++];
        else
            tmp[k++] = q[j++];
    }
    // 将没合并的数据直接倒入 tmp 中
    while (i <= mid) tmp[k++] = q[i++];
    while (j <= r) tmp[k++] = q[j++];
    // 将数据倒回原数组
    for (int i = l, j = 0; i <= r; i++, j++)
    {
        q[i] = tmp[j];
    }
}

时间复杂度:O(N * logN) 空间复杂度:O(N)

接下来,对模板中的 不容易理解的部分 讲解一下

1双指针合并区间

while (i <= mid && j <= r)
{
    // 写法具有稳定性
    if (q[i] <= q[j])
        tmp[k++] = q[i++];
    else
        tmp[k++] = q[j++];
}

这一部分就是 ij 对应的两区间的内容进行比较,让其有序存入 辅助数组 tmp 中:

q:1    4    6    1    3    5
   i        mid   j     
 第一个 1        第二个1
tmp:1    1    3    4    5    6
 第一个1 第二个1

在这一过程中,对于相同值的数据位置保持不变,归并排序是具有 稳定性 的。

2将没合并的内容倒入 tmp

while (i <= mid) tmp[k++] = q[i++];
while (j <= r) tmp[k++] = q[j++];

假如一段区间的内容已经完全存入 tmp 中,另一段区间未存储完毕:

q:1    4    6    1    3
   i        mid   j     
tmp:1    1    3    		此时 右区间已经放置完毕,左区间还剩下 4 6
   第一个1   第二个1
   
左区间剩余元素的最小值为 4,必定大于等于 tmp 数组的最后一个元素,直接将数据倒入 tmp 中
while (i <= mid) tmp[k++] = q[i++];
tmp:1    1    3    4    6

3将数据倒回原数组

for (int i = l, j = 0; i <= r; i++, j++)
{
    q[i] = tmp[j];
}

这里 i = l 而不是 i = 0 的原因是,我们每次归并的可能不是 一整个原数组 ,可能是一段区间,区间从 l 开始,到 r 结束。

如果看完板子还是比较模糊的话,可以下去举个例子画一下归并排序的过程,观察递归到最底层,然后逐渐归并返回。这一过程了解了,这个板子几乎也就吃透了~

三、模板测试

给定你一个长度为 n 的整数数列。

请你使用归并排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式

输入共两行,第一行包含整数 n

第二行包含 n 个整数(所有整数均在 1∼10^9 范围内),表示整个数列。

输出格式

输出共一行,包含 n个整数,表示排好序的数列。

数据范围

1 ≤ n ≤ 100000

输入样例

5
3 1 2 4 5

输出样例

1 2 3 4 5

image-20221207211849779

AC,没问题

四、加练 —— 逆序对的数量

描述

给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i < ja[i] > a[j],则其为一个逆序对;否则不是。

输入格式

第一行包含整数 n,表示数列的长度。

第二行包含 n 个整数,表示整个数列。

输出格式

输出一个整数,表示逆序对的个数。

数据范围

1 ≤ n ≤ 100000
数列中的元素的取值范围 [1, 10^9]

输入样例

6
2 3 4 5 6 1

输出样例

5

思路

这道题的主要思路还是 归并排序

先了解一下什么是 逆序对

例如:5 2 1,5 分别可以 和 2 和 1 构成逆序对:5 25 1。2 可以和 1 构成 逆序对 2 1

对于这题,我们依然是将 数列 分为两个区间:

image-20221207235546813

逆序对出现的位置有 三种情况

  1. 蓝色逆序对:左半边区间的逆序对数量,区间:[l, mid]
  2. 紫色逆序对:右半边区间的逆序对数量,区间:[mid + 1, r]
  3. 红色逆序对:存在于左右半边区间之间,区间不固定

那么,如何快速准确计算出 红色逆序对 的数目?我们需要进行推导:

假设:s1 是序列中,能和 s1 对应位置构成逆序对的数目。

image-20221208000410224

s2 ~ se 的性质和 s1 完全相同,那么对于一整个序列中,逆序对总数就是:s1 + s2 + ... + se

有了这个铺垫,我们继续推导,现在假设区间由于归并排序的原因使 [l, mid][mid + 1, r] 相对有序:

image-20221208001358277

q[i] > q[j] 那么在 i 所在区间中 q[i] 后的数是严格大于等于 q[i] 的,j 所在区间中 q[j] 前的数是严格小于等于 q[j] 的。

一旦满足 q[i] > q[j] 这个条件,那么 q[i] 之后的元素都可以和 q[j] 构成 逆序对

那么它们之间 逆序对的个数 如何计算?

mid 是左区间边界,i 是满足组成逆序对数据的起始位置,那么从 i 开始一共有 mid - i + 1 个元素可以和 q[j] 构成逆序对。

有了这个公式,那么我们只需要在归并的过程中,一旦条件满足左区间元素大于右区间元素,那么从左区间的该位置开始到右区间的位置均可以构成逆序对,随后进行统计就可以。

注意

当序列完全 逆序 时,所能构成的逆序对最多。

假设序列为:n, n - 1, n - 2, ..., 1,一共 n 个数

逆序对的总数就为 n - 1 + n - 2 + ... + 1,由于一个数只能与其之后的数构成逆序对,所以 1 后无元素,无法构成逆序对,等差数列为 n - 1 个数。

根据等差数列求和公式求出逆序对的计算公式:n * (n - 1) / 2

题目给定最大数据为 100000,带入结果为 4,999,950,000,而 int 最大容纳数据为 23亿多,这里有靠近 50 亿,所以定义逆序对的变量时,需要使用 long long

接下来我们看看代码怎么写

#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int q[N], tmp[N], n;

long long merge_sort(int q[], int l, int r)
{
    if (l >= r) return 0;
    
    int mid = l + r >> 1;
    
    // 左区间逆序对数目 + 右区间逆序对数目
    long long res = merge_sort(q, l, mid) + merge_sort(q, mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    // 归并过程 
    while (i <= mid && j <= r)
    {
        if (q[i] <= q[j]) tmp[k++] = q[i++];
        else // q[i] > q[j]
        {
            res += mid - i + 1;
            tmp[k++] = q[j++];
        }
    }
    
    // 倒入 tmp 中
    while (i <= mid) tmp[k++] = q[i++];
    while (j <= r) tmp[k++] = q[j++];
    
    // 倒回 原数组
    for (int i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];
    return res;
}

int main()
{
    cin >> n;
    
    for (int i = 0; i < n; i++) cin >> q[i];
    
    cout << merge_sort(q, 0, n - 1);
    
    return 0;
}

这里的 逆序对计算过程是严格保证有序的

因为一开始归并时,会递归到最底层,从底层开始计算归并然后返回数据的,所以计算过程序列严格有序,不必担心计算发生错误等情况。

另外提一句

其实我们这块计算最多的逆序对情况就是 红色逆序对 ,对于左区间和右区间的逆序对情况,在一开始就会开始递归到底层,从而转变为 红色逆序对 的计算。

如果不清楚可以画一下递归展开图,会更加清晰~

到这里,本篇博客就到此结束了。如果对于模板理解的不清晰的话,最好的方法就是下去自己画图~
如果觉得anduin写的还不错的话,还请一键三连!如有错误,还请指正!
我是anduin,一名C语言初学者,我们下期见!

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

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

相关文章

[附源码]Python计算机毕业设计SSM加油站管理信息系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【论文笔记】InverseForm: A Loss Function for Structured Boundary-Aware Segmentation

论文 标题&#xff1a;InverseForm: A Loss Function for Structured Boundary-Aware Segmentation 收录于&#xff1a;CVPR 2021 论文&#xff1a;[2104.02745] InverseForm: A Loss Function for Structured Boundary-Aware Segmentation (arxiv.org) 代码&#xff1a;Git…

大数据都应用在哪些领域?

大数据被应用较多的领域有哪些&#xff1f;疫情期间大数据技术对于疫情的防控发挥了巨大的作用&#xff0c;抗疫期间多家互联网企业纷纷加强大数据在疫情防控中的应用。小到社区大到部委相关部门都将大数据作为不可或缺的防疫工具&#xff0c;生活中很多方面涉及到大数据由此可…

Scala014--Scala中的函数

一&#xff0c;函数的定义和声明 对于其他计算机语言来说&#xff0c;如Java&#xff0c;python&#xff0c;函数和方法是一样的&#xff0c;但是对于Scala来说&#xff0c;函数和方法并不是同一个概念&#xff0c;方法是类或者是对象的成员&#xff0c;而函数是一个对象。但是…

澳亚集团通过聆讯:毛利率波动,预计利润将下滑,陈荣南为董事长

撰稿|汤汤 来源|贝多财经 近日&#xff0c;港交所披露的信息显示&#xff0c;澳亚集团有限公司&#xff08;下称“澳亚集团”&#xff09;通过港交所聆讯&#xff0c;并披露了聆讯后资料集&#xff08;即招股书&#xff09;&#xff0c;中金公司和星展银行&#xff08;DBS&am…

如何从 Power BI 示例中获取数据以供练习

如果您是 Power BI 初学者, Microsoft Power BI 教程中提供的示例是入门的好地方。 在这篇文章中,我将按照步骤在 excel 中查看示例数据,以便您可以将这些数据用于练习目的。 下载 Excel 文件 首先,在浏览器中打开人力资源数据。文包含有关如何使用数据构建 Power BI 报…

不是我穷,是他真的很好用!

今天猫猫为您推荐一款良心vx小程序——喵盐配音&#xff01;利用小程序的特性&#xff0c;无需安装&#xff0c;即走即用。 这个配音软件超多功能&#xff0c;太强大了&#xff01;操作简单&#xff0c;输入文本&#xff0c;一键配音&#xff0c;小白也会用超多声音主播&#…

12月8日绿健简报,星期四,农历十一月十五

12月8日绿健简报&#xff0c;星期四&#xff0c;农历十一月十五1. 中国铁路&#xff1a;即日起购票乘车及进出站停止查验核酸和健康码&#xff1b;联防联控机制&#xff1a;不再对跨地区流动人员查验健康码&#xff0c;无症状和轻型病例一般采取居家隔离。2. 包头&#xff1a;交…

torchnet 简单使用文档

torchnet 是用于 torch 的代码复用和模块化编程的框架&#xff1a; 主要包含4个部分&#xff1a; Dataset&#xff1a;各种不同方式处理数据。Engine:各种机器学习算法Meter:性能度量指标。 Log&#xff1a;Log&#xff1a;Log&#xff1a; 模块详细分为如下部分&#xff1a;D…

FLStudio2023电脑版安装下载及fl21版本新功能介绍

FL Studio水果简称FL&#xff0c;全称&#xff1a;Fruity Loops Studio&#xff0c;国人习惯叫它"水果"。软件现有版本是 FL Studio 21&#xff0c;已全面升级支持简体中文语言界面 。FL Studio 能让你的计算机就像是全功能的录音室一样&#xff0c;完成编曲、剪辑、…

YourKit Profiler for .NET功能和内存一体探查器

YourKit Profiler for .NET功能和内存一体探查器 Windows和Linux的简单易用内存和性能.NET探查器。 功能和内存一体的.NET探查器 远程和本地评测各种.NET Center和.NET设计应用程序、服务器和Windows服务。 探索测试、开发和制造环境中的性能问题。 与Visual Studio和JetBrains…

一键式开启:IDaaS 日志接入 SLS日志审计发布

背景 什么是IDaaS 应用身份服务IDaaS(Identity as a Service)是阿里云原生身份管理系统&#xff0c;可以统一管理各应用中分散的账号&#xff0c;并集中分配应用访问控制权限&#xff0c;降低低效、重复的账号访问配置和运维工作。IDaaS 旗下的EIAM&#xff08;Enterprise IA…

java计算机毕业设计ssm乡村疫情防控管理系统37804(附源码、数据库)

java计算机毕业设计ssm乡村疫情防控管理系统37804&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#…

时间都去哪了?

在很长一段时间我并不知道怎么去平衡速率和质量之间的关系&#xff0c;我虽然看过不少书和文章告诉我只有保证质量才能保证速率&#xff0c;但我还没有见过反例&#xff0c;我没办法很好地说服别人&#xff0c;我只能看着他们义无反顾的冲向进度&#xff0c;然后抱怨时间不够。…

大数据hadoop_HDFS的shell操作(2)

文章目录1. 基本语法2. hadoop hdfs命令指南3. 常用命令操作指南3.1 准备工作3.2 上传3.3 下载3.4 基本操作1. 基本语法 hadoop fs 具体的命令 或者 hdfs dfs 具体的命令一般是使用hadoop fs 具体的命令&#xff0c;因为是简单好记&#xff0c;容易理解。 2. hadoop hdfs命令…

TypeScript 开发环境搭建

TypeScript是JavaScript类型的超集&#xff0c;它可以编译成纯JavaScript代码&#xff0c;TypeScript可以在任何浏览器&#xff0c;任何计算机和任何操作系统上运行。 目录 依赖环境 搭建步骤 1.新建一个文件夹 2.初始化一个NPM项目 3.安装typescript 包 4.新建一个tscon…

SQLite 基本命令使用方式

本文介绍创建一个简单的数据库&#xff0c;并能够在需要的时间和地点快速使用它们。SQLite 在世界范围内的许多设备中使用。 什么是SQLite&#xff1f; SQLite是一个进程内的库&#xff0c;实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的…

Spring Cloud Alibaba整合Sentinel进行服务熔断降级

一、下载Sentinel Dashboard控制台服务 Releases alibaba/Sentinel GitHub 一样的&#xff0c;根据自己的Spring Cloud Alibaba版本下载相应版本的Sentinel 启动服务&#xff0c;可以指定端口 java -Dserver.port8849 -Dcsp.sentinel.dashboard.serverlocalhost:8849 -Dp…

记录下QT读取串口数据时遇到的问题

一、如果使用QT读取串口数据 使用定时器定时发送信号&#xff0c;然后调用槽函数来读取串口数据&#xff0c;串口数据读取过程加锁。 timer1 startTimer(15); connect(this, SIGNAL(callCapData()), this, SLOT(CapData()));void ecgfrom::timerEvent(QTimerEvent *event) {…

Allegro如何批量把器件放在指定的格点上操作指导

Allegro如何批量把器件放在指定的格点上操作指导 Allegro支持批量把器件放在指定的格点上,具体操作如下 以下图为例,器件在小数点位以后的格点上,如果只是个别器件,只需要切换好格点,并且手动移动下就可以了,如果有大量的器件都是这样,这样会比较费时 选择File-change…