排序算法——归并排序(递归与非递归)

news/2024/11/17 16:38:04/

归并排序

以升序为例

文章目录

  • 归并排序
    • 基本思想
    • 核心步骤
    • 递归写法
      • 实现代码
    • 非递归
      • 处理边界情况
      • 实现代码
    • 时间复杂度

基本思想

  • 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
  • 如果对两个有序序列的归并操作还不太熟悉,建议先看看合并两个有序链表

核心步骤

在这里插入图片描述

  • 由上图我们可以看到,归并排序首先要对待排序列不断二分,直到分成不可分割的子序列(即只有一个元素的序列,相当于有序)
  • 然后,再对有序的子序列不断进行归并操作,最后得到完全有序的序列。
  • 归并排序有递归和非递归两种写法,接下来我们来讨论如何实现的具体细节:

递归写法

  • 首先我们要注意,在进行归并操作时,为了防止原序列的元素被覆盖而导致排序错误,我们需要向内存申请一块空间用来临时存放合并的序列,同时,由于归并的此时不止一次,为防止多次申请内存而导致效率不高,我们直接向内存申请一块和原序列大小相等的空间
void MergeSort(int *nums, int numsSize)
{int* temp = (int*)malloc(sizeof(int) * numsSize);…………;free(temp);                           
}
  • 同时,归并排序在进行归并操作时需要知道每个子序列的区间,由于递归参数的限制,我们需要再定义一个子函数MergeSort(),并对这个子函数递归
void _MergeSort(int *nums, int *temp, int left, int right)
{…………;
}void MergeSort(int *nums, int numsSize)
{int* temp = (int*)malloc(sizeof(int) * numsSize);_MergeSort(nums, temp, 0, numsSize - 1);free(temp);                           
}
  • 易知,当子序列长度为1时,就可以不再进行二分
void _MergeSort(int *nums, int *temp, int left, int right)
{if (left >= right)return;…………;
}
  • 对待排序列的左半部分和右半部分不断递归分割
void _MergeSort(int *nums, int *temp, int left, int right)
{if (left >= right)return;int mid = (right - left) / 2 + left;_MergeSort(nums, temp, left, mid);_MergeSort(nums, temp, mid + 1, right);……………;
}
  • 接下来,就是对两个有序序列的合并操作
  • 注:可以走到合并这一步说明待合并的两个序列[left,mid]和[mid + 1,right]是有序的,存在两种情况:
    • 情况一:例如序列{9,2},进入函数_MergeSort()后,其子序列是单个数字,满足left >= right的条件,直接退出递归,开始合并。
    • 情况二:例如序列{9,2,5,4},进入函数_MergeSort()后,子序列{9,2}递归,合并后退出递归,然后子序列{5,4}递归,合并,退出递归,最后就变成了两个有序序列{2,9}和{4,5}的合并
    • 建议对递归不是很清楚的小伙伴可以尝试画画递归展开图,这对了解递归逻辑大有裨益。
void _MergeSort(int *nums, int *temp, int left, int right)
{if (left >= right)return;//递归分割int mid = (right - left) / 2 + left;_MergeSort(nums, temp, left, mid);_MergeSort(nums, temp, mid + 1, right);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = left;//归并while (begin1 <= end1 && begin2 <= end2){if (nums[begin1] > nums[begin2])temp[index++] = nums[begin2++];elsetemp[index++] = nums[begin1++];}while (begin1 <= end1)temp[index++] = nums[begin1++];while (begin2 <= end2)temp[index++] = nums[begin2++];//将temp暂时存储的数据覆盖待排序列nums原有位置的数据,实现待排序列区间有序for (int i = left; i <= right; i++)nums[i] = temp[i];
}

实现代码

void _MergeSort(int *nums, int *temp, int left, int right)
{if (left >= right)return;//递归分割int mid = (right - left) / 2 + left;_MergeSort(nums, temp, left, mid);_MergeSort(nums, temp, mid + 1, right);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = left;//归并while (begin1 <= end1 && begin2 <= end2){if (nums[begin1] > nums[begin2])temp[index++] = nums[begin2++];elsetemp[index++] = nums[begin1++];}while (begin1 <= end1)temp[index++] = nums[begin1++];while (begin2 <= end2)temp[index++] = nums[begin2++];//将temp暂时存储的数据覆盖待排序列nums原有位置的数据(拷贝回去),实现待排序列区间有序for (int i = left; i <= right; i++)nums[i] = temp[i];
}void MergeSort(int *nums, int numsSize)
{int* temp = (int*)malloc(sizeof(int) * numsSize);_MergeSort(nums, temp, 0, numsSize - 1);free(temp);                           
}

非递归

  • 我们可以直接用循环解决问题,如图所示:

在这里插入图片描述

  • 由上面的递归分析可以知道,两个单个数字可以直接合并成一个有序序列。因此我们定义gap,表示每次合并的两个序列长度为gap,gap从1递增,直到不能满足条件gap < numsSize,然后就进行和递归相同的合并操作就可以了
void MergeSort(int* nums, int numsSize)
{int* temp = (int*)malloc(sizeof(int) * numsSize);int gap = 1;while (gap < numsSize){/*因为每次是对两个有序序列进行合并因此每次合并过后i应该跳过两个序列长度*/for (int i = 0; i < numsSize; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;int index = begin1;//归并while (begin1 <= end1 && begin2 <= end2){if (nums[begin1] < nums[begin2])temp[index++] = nums[begin1++];elsetemp[index++] = nums[begin2++];}while(begin1 <= end1)temp[index++] = nums[begin1++];while(begin2 <= end2)temp[index++] = nums[begin2++];//将temp暂时存储的数据覆盖待排序列nums原有位置的数据(拷贝回去),实现待排序列区间有序for (int j = i; j <= end2; j++)nums[j] = temp[j];}gap *= 2;}free(temp);
}

处理边界情况

  • 看起来好像很简单,但上面的代码仍存在些许bug,仍需要我们谨慎处理
  • 我们来看一个情况,如果给的待排数组是{5,4,3,2,9,7,1,6,8}

在这里插入图片描述

  • 如果给的待排数组是{5,4,3,2,9,7

在这里插入图片描述

int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
int index = begin1;/*如果右半区间不存在,只有左半区间说明待合并的只有一个区间显然没有合并的必要,直接退出合并循环即可
*/
if (begin2 >= numsSize)break;//如果右半区间算多了,那么对end2进行修正
if (end2 >= numsSize)end2 = numsSize - 1;

实现代码

void MergeSort(int* nums, int numsSize)
{int* temp = (int*)malloc(sizeof(int) * numsSize);int gap = 1;while (gap < numsSize){/*因为每次是对两个有序序列进行合并因此每次合并过后i应该跳过两个序列长度*/for (int i = 0; i < numsSize; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;int index = begin1;/*如果右半区间不存在,只有左半区间说明待合并的只有一个区间显然没有合并的必要,直接退出合并循环即可*/if (begin2 >= numsSize)break;//如果右半区间算多了,那么对end2进行修正if (end2 >= numsSize)end2 = numsSize - 1;//归并while (begin1 <= end1 && begin2 <= end2){if (nums[begin1] < nums[begin2])temp[index++] = nums[begin1++];elsetemp[index++] = nums[begin2++];}while(begin1 <= end1)temp[index++] = nums[begin1++];while(begin2 <= end2)temp[index++] = nums[begin2++];//将temp暂时存储的数据覆盖待排序列nums原有位置的数据(拷贝回去),实现待排序列区间有序for (int j = i; j <= end2; j++)nums[j] = temp[j];}gap *= 2;}free(temp);
}

时间复杂度

  • 易得,合并两个有序序列的时间复杂度为O(N)
  • 由于是对待排序列的不断二分,知道分割为不可分割的子序列,因此这一过程的时间复杂度为O(log2N)
  • 因此归并排序的时间复杂度为O(NlogN)

http://www.ppmy.cn/news/539667.html

相关文章

按头安利 好看又实用的毛笔书法字体素材看这里

前方干货满满&#xff0c;建议先收藏再看哦&#xff01;为大家整理毛笔书法字体素材&#xff0c;总有满足你需求的一款&#xff0c;除此之外&#xff0c;免费&#xff0c;资源质量好&#xff0c;一键打包下载&#xff0c;你还不心动吗&#xff1f; 本人曾经也是废大把时间寻找…

毛笔笔刷书法签名手写字体设计 Brightwall – Brush Signature Font

Brightwall刷字体是用天然刷子做成&#xff0c;笔刷字体的纹理将使你的设计更加美丽和强大。 这种字体适合任何设计&#xff0c;如品牌、报价、t恤打印等。 素材特点&#xff1a; 多语言字符17种绑扎PUA编码数字和标点符号(OpenType标准)

Qt 笔锋 毛笔 钢笔 蜡笔 4k流畅画笔 Demo

基于Qt实现 毛笔、钢笔、蜡笔、4k不卡顿画笔 体验Demo下载链接&#xff1a;https://download.csdn.net/download/u012532263/15709871 需要源码 联系方式: QQ&#xff1a;550993637 QQ邮箱&#xff1a;550993637qq.com &#xff08;着急发邮件&#xff0c;qq不常上&#xff0…

html设置日文字体,怎样熟练使用各种日文字体

日文字体大体上可以分为“明朝”和“Gothic”两大类。明朝体的笔画竖比横粗&#xff0c;横的右端带有尖角折。而Gothic体则是横和竖差不多粗&#xff0c;几乎没有尖角折。 长篇的日语文章要选择可读性高的“明朝体” 会议总结或小论文、企划书等长篇文章更注重可读性(容易看)&a…

android 仿手写字体下载,手写毛笔字体在线生成器-手写毛笔字软件下载v1.0 安卓版-西西软件下载...

&#xfeff;手写毛笔字软件是一款可以帮你制作非常好看的毛笔字体的软件&#xff0c;很多人看到一些人的贴吧签名档就是一副非常好看的毛笔字体的图&#xff0c;其实这些字并不是他们自己写的而是通过这款软件合成制作的。你可以通过软件结合相应的素材生成好看的毛笔字体&…

怎么转换书法字体?教你快速转换毛笔字体

毛笔字是我国的传统文化艺术之一&#xff0c;许多人都喜欢将毛笔字体运用到现在的图片设计中来提升画面的设计感和美感&#xff0c;当我们在使用毛笔字表现的时候&#xff0c;很多人都会上网寻找合适的字体&#xff0c;但有时候却找不到让我们满意的毛笔字体时该怎么办呢?我来…

ps写php,ps毛笔字体怎么做

ps毛笔字体怎么做&#xff1f; 1、开启Photoshop之后&#xff0c;新建一个PS文件&#xff0c;接着先行间接用粗字体打出要制成书法毛笔字的文字内容&#xff0c;例如试点之中做的两个字"风云"&#xff0c;接着可于网上找一些能用到的毛笔写的笔画&#xff0c;制成均匀…

android 源代码 毛笔,android中实现毛笔效果(View 中绘图)

最近有一个项目设计一个APP实现通过触摸屏实现毛笔写字效果。传统的绘画板程序直接通过Path的moveTo和LineTo便可实现简单的线条绘画程序。然而要达到毛笔的笔锋效果则需要更为详细点的设计。我的实现思路是通过以触摸事件DOWN、MOVE、UP中的每一个点为圆心画圆&#xff0c;除此…