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

这篇具有很好参考价值的文章主要介绍了【初阶算法4】——归并排序的详解,及其归并排序的扩展。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

学习目标:

学习内容:

一、介绍归并排序

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有序表为止。下面小编将画图带大家理解:

【初阶算法4】——归并排序的详解,及其归并排序的扩展,初阶算法,算法,归并排序,归并排序的代码,小和问题,逆序对问题

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

【初阶算法4】——归并排序的详解,及其归并排序的扩展,初阶算法,算法,归并排序,归并排序的代码,小和问题,逆序对问题

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);
}

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

       小编觉得在数组对问题可能使用归并排序,尤其是一个数组对中满足一定条件,左边的左边小于右边的坐标时,可以考虑考虑归并排序算法的思想。文章来源地址https://www.toymoban.com/news/detail-712983.html


学习产出:

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

到了这里,关于【初阶算法4】——归并排序的详解,及其归并排序的扩展的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【排序算法】 归并排序详解!深入理解!思想+实现!

    【排序算法】 归并排序详解!深入理解!思想+实现!

    🎥 屿小夏 : 个人主页 🔥个人专栏 : 算法—排序篇 🌄 莫道桑榆晚,为霞尚满天! ​ 什么是归并?通过归并排序就能让数据有序?分治法是怎么在归并排序上应用的?本文将对归并排序进行细致入微的讲解,庖丁解牛般让你彻底明白归并排序! 归并排序(MERGE-SORT)是建

    2024年02月08日
    浏览(10)
  • 【算法】归并排序 详解

    【算法】归并排序 详解

    排序: 排序,就是使一串记录,按照其中的某个或某些的大小,递增或递减的排列起来的操作。 稳定性: 假定在待排序的记录序列中,存在多个具有相同的的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中, r[i] = r[j], 且 r[i] 在 r[j] 之前,而

    2024年02月09日
    浏览(6)
  • 排序算法-归并排序(含C语言代码示例)

            归并排序是一种基于分治思想的经典排序算法,其主要思想是将待排序的数组分割成两个子数组,分别对这两个子数组进行递归排序,然后将排好序的子数组合并起来得到最终有序数组。整个归并排序的过程可以分为三个步骤:分割、排序和合并。         首

    2024年01月16日
    浏览(8)
  • 【排序算法】 归并排序详解!深入理解!思想+源码实现!

    【排序算法】 归并排序详解!深入理解!思想+源码实现!

    🎥 屿小夏 : 个人主页 🔥个人专栏 : 算法—排序篇 🌄 莫道桑榆晚,为霞尚满天! ​ 什么是归并?通过归并排序就能让数据有序?分治法是怎么在归并排序上应用的?本文将对归并排序进行细致入微的讲解,庖丁解牛般让你彻底明白归并排序! 归并排序(MERGE-SORT)是建

    2024年02月06日
    浏览(7)
  • ①归并排序、快速排序 、堆排序、计数排序[算法、代码模板、面试题]

    ①归并排序、快速排序 、堆排序、计数排序[算法、代码模板、面试题]

    个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ ⚪步骤 归并排序 : 归并排序是一种分治法(Divide and Conquer)的经典排序算法,它的基本思想是将原始数组

    2024年02月04日
    浏览(10)
  • [排序算法]:归并排序(Merge Sort)(递归与非递归实现详解)

    [排序算法]:归并排序(Merge Sort)(递归与非递归实现详解)

            归并排序,是创建在归并操作上的一种有效的排序算法。算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。归并排序思路简单,速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列。

    2024年01月20日
    浏览(17)
  • 七大排序算法——归并排序,通俗易懂的思路讲解与图解(完整Java代码)

    七大排序算法——归并排序,通俗易懂的思路讲解与图解(完整Java代码)

    排序:所谓排序,就是使一串记录,按照其中的某个或某些的大小,递增或递减的排列起来的操作。 上述待排序的数中,有两个5。 将 前面 的5标记一个a, 将 后面 的5标记一个b。 通过算法进行排序后,这一组数就有序了, 但是要看两个相同的5的位置是否有改变。

    2024年02月15日
    浏览(10)
  • 【数据结构初阶】八大排序(三)——归并排序&&计数排序

    【数据结构初阶】八大排序(三)——归并排序&&计数排序

    大家好我是沐曦希💕 往期博客:【数据结构初阶】八大排序(一)——希尔排序堆排序直接插入排序直接选择排序 【数据结构初阶】八大排序(二)——快速排序冒泡排序 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一

    2024年02月03日
    浏览(36)
  • 详解八大排序算法-附动图和源码(插入,希尔,选择,堆排序,冒泡,快速,归并,计数)

    详解八大排序算法-附动图和源码(插入,希尔,选择,堆排序,冒泡,快速,归并,计数)

    目录 🍏一.排序的概念及应用🍏  1.排序的概念  2.排序的应用  3.常用的排序算法 🍎二.排序算法的实现🍎 1.插入排序 1.1直接插入排序 1.2希尔排序(缩小增量排序) 2.选择排序 2.1直接选择排序 2.2堆排序 3.比较排序 3.1冒泡排序 3.2快速排序  递归版本 快速排序递归优化 快速

    2024年02月01日
    浏览(10)
  • 【算法与数据结构】归并排序的代码实现(详细图解)以及master公式的讲解

    【算法与数据结构】归并排序的代码实现(详细图解)以及master公式的讲解

    目录 1、归并排序  1.1、算法描述  1.2、图解说明 2、代码实现  3、master公式 3.1、公式以及结论 3.2、适用于某些特殊的递归 3.3、计算归并排序的时间复杂度 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用 递归 或者说是 分治法 (Divide and Conquer)的一个非

    2024年02月08日
    浏览(15)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包