【初阶算法4】——归并排序的详解,及其归并排序的扩展

news2024/10/6 3:06:21

目录

前言

学习目标:

学习内容:

一、介绍归并排序

1.1 归并排序的思路

1.2 归并排序的代码

1.2.1 mergesort函数部分 

1.2.2 process函数部分 

1.2.3 merge函数部分 

二、AC两道经典的OJ题目

题目一:逆序对问题

题目二:小和问题 

三、练习一道LeetCode的题目

四、总结在什么情况下使用归并排序的算法

学习产出:


前言

💓作者简介: 加油,旭杏,目前大二,正在学习C++数据结构等👀
💓作者主页:加油,旭杏的主页👀

⏩本文收录在:再识C进阶的专栏👀

🚚代码仓库:旭日东升 1👀

🌹欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖

学习目标:

       在这一篇博客中,我们要学习排序算法中比较重要的三个排序中的其中一个,就是归并排序,并学会使用代码进行编写归并排序,之后能够AC两道经典的OJ题目,最后是刷几道LeetCode的题目,这就是本博客的学习目标!


学习内容:

通过上面的学习目标,我们可以列出要学习的内容:

  1. 学习归并排序的思想
  2. 使用代码进行编写归并排序
  3. AC两道经典的OJ题目
  4. 练习几道LeetCode的题目
  5. 总结在什么情况下使用归并排序的算法

一、介绍归并排序

       在之前的学习中,我们可能或多或少的接触过排序算法,也知道一些排序算法:冒泡排序选择排序插入排序。不过这些排序有共同点——时间复杂度为O(N * N),时间上不是那么有效,我们需要进行进一步的优化,从而就有了我们这一篇博客讲述的归并排序,其时间复杂度为:O(N * logN)空间复杂度为:O(N)

1.1 归并排序的思路

       归并排序,顾名思义,“归并”的含义是将两个两个以上的有序表合并一个新的有序表。假设待排序数表有N个记录,则可将其视为N个有序的子表,每一个子表的长度是1,然后两两归并,得到[ N / 2 ]个长度为2或1的有序表;继续两两归并……如此重复,直到合并成一个长度为N有序表为止。下面小编将画图带大家理解:

       而这个排序刚开始有点像这个相反的做法,其思路是:先将这一大长串数组分割,因为其是一个递归的过程,就是将数组一直分割,直到每一个数组长度为1,此时每一个数组都是有序的;之后要开始合并数组,合并数组时将进行比较,看需要来判断是升序或降序,合并的时候就如同上方的合并一样,最后能够得出一个有序的数组。

1.2 归并排序的代码

这个算法有三个部分组成,请看下面我一一为大家进行讲解:

1.2.1 mergesort函数部分 

void mergesort(int arr[], int left, int right)
{
    int sz = right - left + 1; //计算数组的长度
    if(arr == NULL || sz < 2)  //如果数组的首元素不存在,则不用排序;
        return ;               //如果数组的长度只有一个,则也不用排序
    process(arr, left, right); //进入process函数部分
}

1.2.2 process函数部分 

void process(int arr[], int left, int right)
{
    int sz = right - left + 1;   //与mergesort函数的部分作用是一样的
    if(arr == NULL || sz < 2)
        return ;
//分割数组
    int mid = left + ((right - left) >> 1);  //计算出这段数组中正中心的位置坐标
    process(arr, left, mid);     //递归过程中,将数组分为左右部分,这是左部分
    process(arr, mid + 1, right);//这是右部分
    merge(arr, left, mid, right);
}

1.2.3 merge函数部分 

void merge(int arr[], int left, int mid, int right)
{
    int sz = right - left + 1;
    int* help = (int*)malloc(sizeof(int) * sz); //构造辅助数组
    int i = 0;
    int p1 = left;  //建立指针
    int p2 = mid + 1;
    while(p1 <= mid && p2 <= right) //如果左指针与右指针都不越界,则进入循环
    {
        help[i++] = arr[p1] > arr[p2] ? arr[p1++] : arr[p2++]; //进行比较,交换数据
    }
    while(p1 <= mid) //将左边剩余的数据拷贝到辅助数组中
    {
        help[i++] = arr[p1++];
    }
    while(p2 <= right) //将右边剩余的数据拷贝到辅助数组中
    {
        help[i++] = arr[p2++];
    }
    for(int i = 0; i < sz; i++) //最后将辅助数组中的数据转移到原数组中
    {
        arr[left + i] = help[i];
    }
}

二、AC两道经典的OJ题目

题目一:逆序对问题

题目:

       在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

思路:

       在这道题中,我们要注意题目中标红的描述,这个逆序对永远都是前一个数字的坐标位置永远小于后一个数字的坐标位置。这一点就和归并排序不谋而合,归并排序算法总是将左边的数字与右边的数字进行比较,然后进行排序。而这道题要求的是逆序对的个数,我们可以在进行比较的时候进行计数,这就是大致思路。

       将这个数组进行降序排序还是逆序排序呢?如果是降序排序时,我们将左边有右边进行比较,如果左边大于右边,则大于右边所有的数字,进行计数即可;如果是升序排序可能会出现漏项的情况,自然排除升序,采用降序。

代码:

int merge(int arr[], int left, int mid, int right)
{
    int sz = right - left + 1;
    int* help = (int*)malloc(sizeof(int) * sz);
    int p1 = left;
    int p2 = mid + 1;
    int i = 0;
    int ret = 0;
    while(p1 <= mid && p2 <= right)
    {
        ret += arr[p1]>arr[p2]?(right - p2 + 1):0; //如果左边的数字大于右边的数字,则计算右边
//一共有多少数字
        help[i++] = arr[p1] > arr[p2]?arr[p1++]:arr[p2++];
    }
    while(p1 <= mid)
    {
        help[i++] = arr[p1++];
    }
    while(p2 <= right)
    {
        help[i++] = arr[p2++];
    }
    for(int i = 0; i < sz; i++)
    {
        arr[left + i] = help[i];
    }
    return ret;
}

int process(int arr[], int left, int right)
{
    int sz = right - left + 1;
    if(arr == NULL || sz < 2)
        return 0;
    int mid = left + ((right - left) >> 1);
    return process(arr, left, mid) + process(arr, mid + 1, right) + merge(arr, left, mid, right);
}

int revOrder(int arr[], int left, int right)
{
    int sz = right - left + 1;
    if(arr == NULL || sz < 2)
        return 0;
    return process(arr, left, right);
}

int reversePairs(int* nums, int numsSize){
    int left = 0;
    int right = numsSize - 1;
    int ans = revOrder(nums, left, right);
    return ans;
}

题目二:小和问题 

题目:

       在一个数组中,每一个数左边比当前的数小进行累加起来,叫做这个数组的小和,求一个数组的小和。

思路:

       同理,看题目中被标红的描述,进行降序排序,如果左边的数字小于右边的数字,则将左边的数字乘右边有多少个大于他的个数,进行累加即可。

代码:

int merge(int arr[], int left, int mid, int right)
{
	int sz = right - left + 1;
	int* help = (int*)malloc(sizeof(int) * sz);
	int i = 0;
	int p1 = left;
	int p2 = mid + 1;
	int count = 0;
	while (p1 <= mid && p2 <= right)
	{
		count += arr[p1] < arr[p2] ? (right - p2 + 1)*arr[p1] : 0;
		help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
	}
	while (p1 <= mid)
	{
		help[i++] = arr[p1++];
	}
	while (p2 <= right)
	{
		help[i++] = arr[p2++];
	}
	for (int i = 0; i < sz; i++)
	{
		arr[left + i] = help[i];
	}
	return count;
}

int process(int arr[], int left, int right)
{
	int sz = right - left + 1;
	if (arr == NULL || sz < 2)
		return 0;
	int mid = left + ((right - left) >> 1);
	return process(arr, left, mid) + process(arr, mid + 1, right) + merge(arr, left, mid, right);
}
int reverseOrder(int arr[], int left, int right)
{
	int sz = right - left + 1;
	if (arr == NULL || sz < 2)
		return 0;
	return process(arr, left, right);
}

int main()
{
	int arr[] = { 1,3,4,2,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int left = 0;
	int right = sz - 1;
	int ans = reverseOrder(arr, left, right);
	printf("%d\n", ans);
	return 0;
}

三、练习一道LeetCode的题目

题目:

       给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。你需要返回给定数组中的重要翻转对的数量。

思路:

       前面的操作基本一样,只需将merge函数部分进行修改即可,将左边的指针不动,一一右边的数字进行比较,如果条件成立,就将指针向右移动一位,如果不成立跳出,结果加上右指针减去初始位置的个数

代码:

int process(int arr[], int left, int right)
{
    int sz = right - left + 1;
    if(arr == NULL || sz < 2)
        return 0;
    int mid = left + ((right - left) >> 1);
    int n1 = process(arr, left, mid);
    int n2 = process(arr, mid + 1, right);
    int ret = n1 + n2;
    int p1 = left;
    int p2 = mid + 1;
    int i = 0;
    while(p1 <= mid)
    {
        while(p2 <= right && (long long)arr[p1]>2*(long long)arr[p2])
        p2++;
        ret += (p2 - mid - 1);
        p1++;
    }
    p1 = left;
    p2 = mid + 1;
    int* help = (int*)malloc(sizeof(int) * sz);
    while(p1 <= mid && p2 <= right)
    {
        help[i++] = arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
    }
    while(p1 <= mid)
    {
        help[i++] = arr[p1++];
    }
    while(p2 <= right)
    {
        help[i++] = arr[p2++];
    }
    for(int i = 0; i < sz; ++i)
    {
        arr[left + i] = help[i];
    }
    return ret;
}

int mergeSort(int arr[], int left, int right)
{
    int sz = right - left + 1;
    if(arr == NULL || sz < 2)
        return 0;
    return process(arr, left, right);
}

int reversePairs(int* nums, int numsSize){
    int left = 0;
    int right = numsSize - 1;
    return mergeSort(nums, left, right);
}

四、总结在什么情况下使用归并排序的算法

       小编觉得在数组对问题可能使用归并排序,尤其是一个数组对中满足一定条件,左边的左边小于右边的坐标时,可以考虑考虑归并排序算法的思想。


学习产出:

  1. 学习归并排序的思想
  2. 使用代码进行编写归并排序
  3. AC两道经典的OJ题目
  4. 练习几道LeetCode的题目
  5. 总结在什么情况下使用归并排序的算法

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

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

相关文章

笔记本选购指南

大学生笔记本电脑选购指南 文章目录 笔记本分类指标排行 了解自身需求理工科文科艺术总结 参考指标品牌CPU显卡屏幕其他 购买渠道推荐游戏本Redmi G 锐龙版联想G5000惠普光影精灵9天选4锐龙版联想R7000P暗影精灵9联想拯救者R9000P 全能本华硕无畏PRO15联想小新Pro14 2023 轻薄本…

react ant ice3 实现点击一级菜单自动打开它下面最深的第一个子菜单

1.问题 默认的如果没有你的菜单结构是这样的&#xff1a; [{children: [{name: "通用配置"parentId: "1744857774620672"path: "basic"}],name: "系统管理"parentId: "-1"path: "system"} ]可以看到每层菜单的p…

期权投资的优势有哪些方面?

随着金融市场的不断演变&#xff0c;越来越多的金融衍生品出现在人们的视线中&#xff0c;特别是上证50ETF期权可以做空T0的交易模式吸引了越来越多的朋友&#xff0c;那么期权投资的优势有哪些方面&#xff1f; 期权是投资市场中一个非常重要的投资方式&#xff0c;期权投资能…

SOLIDWORKS装配体如何使用全局变量

客户痛点&#xff1a;随着人力资源价格的增长&#xff0c;设计的时间需要减少时间&#xff0c;提高设计效率。 数据问题&#xff1a;以前单个数据都需要建立单独的数据结构&#xff0c;装配体的模型都要重新建立。 需要解决的问题&#xff1a;能够快速地完成3D模型及装配体的…

TensorFlow 03(Keras)

一、tf.keras tf.keras是TensorFlow 2.0的高阶API接口&#xff0c;为TensorFlow的代码提供了新的风格和设计模式&#xff0c;大大提升了TF代码的简洁性和复用性&#xff0c;官方也推荐使用tf.keras来进行模型设计和开发。 1.1 tf.keras中常用模块 如下表所示: 1.2 常用方法 …

机器学习——协同过滤算法(CF)

机器学习——协同过滤算法&#xff08;CF&#xff09; 文章目录 前言一、基于用户的协同过滤1.1. 原理1.2. 算法步骤1.3. 代码实现 二、基于物品的协同过滤2.1. 原理2.2. 算法步骤2.3. 代码实现 三、比较与总结四、实例解析总结 前言 协同过滤算法是一种常用的推荐系统算法&am…

清理 Ubuntu 系统的 4 个简单步骤

清理 Ubuntu 系统的 4 个简单步骤 现在&#xff0c;试试看这 4 个简单的步骤&#xff0c;来清理你的 Ubuntu 系统吧。 这份精简指南将告诉你如何清理 Ubuntu 系统以及如何释放一些磁盘空间。 如果你的 Ubuntu 系统已经运行了至少一年&#xff0c;尽管系统是最新的&#xff0c;…

2003-2022年黄河流域TCI、VCI、VHI、TVDI逐年1km分辨率数据集

摘要 黄河流域大部分属于干旱、半干旱气候,先天水资源条件不足,是中国各大流域中受干旱影响最为严重的流域。随着全球环境和气候变化,黄河流域的干旱愈加频繁,对黄河流域的干旱监测研究已经成为当下的热点。本数据集基于MODIS植被和地表温度产品,通过对逐年数据进行去云、…

Mendix使用Upload image新增修改账户头像

学习Mendix中级文档&#xff0c;其中有个管理我的账号功能&#xff0c;确保账号主任可以修改其头像&#xff0c;接下来记录如何实现账户头像的上传和修改。根据文档的步骤实现功能&#xff5e;&#xff5e; 新建GeneralExtentions模块&#xff0c;给GeneralExtentions添加两个模…

MapTR v2文章研读

MapTR v2论文来了&#xff0c;本文仅介绍v2相较于v1有什么改进之处&#xff0c;如果想了解v1版本的论文细节&#xff0c;可见链接。 相较于maptr&#xff0c;maptr v2改进之处&#xff1a; 在分层query机制中引进解耦自注意力机制&#xff0c;有效降低了内存消耗&#xff1b;…

Spring中如何解决循环依赖问题

一、什么是循环依赖 循环依赖也叫循环引用&#xff0c;是指bean之间形成相互依赖的关系&#xff0c;由此&#xff0c;bean对象在属性注入时便会产生循环。这种循环依赖会导致编译器无法编译代码&#xff0c;从而无法运行程序。为了避免循环依赖&#xff0c;我们在开发过程中需…

视频号视频下载工具有那些?我们怎么下载视频号里面的视频

本篇文章给大家谈谈视频号视频下载工具&#xff0c;以及视频号视频如何下载?对应的知识点&#xff0c;希望对各位有所帮助。 视频号里面的视频可以下载吗&#xff1f; 视频号官方首先是不提供下载功能的&#xff0c;但是很多第三方可以提供视频号的视频下载功能。 早期版本视…

【力扣每日一题】2023.9.12 课程表Ⅳ

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 今天是课程表系列题目的最后一题&#xff0c;因为我在题库里找不到课程表5了&#xff0c;所以今天的每日一题就是最后一个课程表了。 题…

小节5:Python列表list常用操作

1、对列表的基本认知&#xff1a; 列表list&#xff0c;是可变类型。比如&#xff0c;append()函数会直接改变列表本身&#xff0c;往列表里卖弄添加元素。所以&#xff0c;list_a list_a.append(123)就是错误的。如果想删除列表中的元素&#xff0c;可以用remove()函数&…

基于微信小程序的宠物寄养平台,附源码、数据库

1. 简介 本文正是基于微信小程序开发平台&#xff0c;针对宠物寄养的需求,本文设计出一个包含寄养家庭分类、寄养服务管理、宠物档案、交流论坛的微信小程序,以此帮助宠物寄养的实现,促进宠物寄养工作的进展。 2 开发技术 微信小程序的运行环境分为渲染层和逻辑层&#xff0…

仿照Everything实现的文件搜索工具--SearchEverything

一、项目介绍 项目名称&#xff1a;SearchEverything 项目简介&#xff1a;SearchEverything是仿照Everything实现的一款桌面级的文件搜索软件,它是Everything的增强版&#xff0c;支持跨平台的使用。 项目功能&#xff1a; 1.选择文件夹后&#xff0c;多线程扫描文件夹下的…

学会这个技能,写字楼立马高级起来!

在当今现代化社会中&#xff0c;写字楼已成为商业和行政活动的中心。成千上万的人们每天涌入这些高楼大厦&#xff0c;从事各种各样的工作&#xff0c;以实现公司和组织的目标。然而&#xff0c;与这种繁忙的办公环境一样&#xff0c;也带来了一系列的安全挑战和管理难题。 随着…

【大数据之Kafka】十一、Kafka消费者及消费者组案例

1 独立消费者案例&#xff08;订阅主题&#xff09; &#xff08;1&#xff09;需求&#xff1a;创建一个独立消费者&#xff0c;消费 first 主题中数据。 &#xff08;2&#xff09;分析&#xff1a; 注意&#xff1a;在消费者 API 代码中必须配置消费者组 id。命令行启动消…

算法通关村第13关【青铜】| 数字与数学基础问题

数字统计专题 1.数组元素积的符号 思路&#xff1a;每回碰到负数就取反 class Solution {public int arraySign(int[] nums) {int res nums[0];if(nums[0]>0){res 1;}else if(nums[0]<0){res -1;}else{return res;}for(int i 1;i<nums.length;i){if(nums[i]<…

Linux基本认识

一、Linux基本概念 Linux 内核最初只是由芬兰人林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;在赫尔辛基大学上学时出于个人爱好而编写的。 Linux 是一套免费使用和自由传播的类 Unix 操作系统&#xff0c;是一个基于 POSIX 和 UNIX 的多用户、多任务、支持多线程和多…