C语言分析基础排序算法——归并排序

目录

归并排序

递归版本

非递归版本

非递归版本的问题

归并排序小优化


归并排序

归并排序,分为分治以及合并,分治部分可以使用递归或者非递归完成,归并排序的基本思路是:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并

递归版本

递归版本的归并排序思路如下:先将数组分为不可再分割的只有一个数据的部分,再取小的部分进行尾插,每排序一次就将排序好的数据拷贝到原来的数组中

//以下面的数组为例
int data[] = { 10,5,6,9,1,3,4,7 };

void _MergeSort(int* data, int* tmp, int left, int right)
{//确定递归结束条件if (left == right){return;}//分割数组,首先确定当前数组的中间位置int mid = (left + right) / 2;_MergeSort(data, tmp, left, mid);_MergeSort(data, tmp, mid + 1, right);//取小的数值尾插到tmp数组中int begin1 = left;int end1 = mid;int begin2 = mid + 1;int end2 = right;int i = left;while (begin1 <= end1 && begin2 <= end2){if (data[begin1] < data[begin2]){tmp[i++] = data[begin1++];}else{tmp[i++] = data[begin2++];}}//存在一个数组先走完的情况while (begin1 <= end1){tmp[i++] = data[begin1++];}while (begin2 <= end2){tmp[i++] = data[begin2++];}//排序完之后将tmp数组中的数据拷贝回原来的数组memcpy(data + left, tmp + left, sizeof(int) * (right - left + 1));
}//归并排序递归版
void MergeSort(int* data, int sz)
{//因为需要将排序好的数据重新拷贝到原来的数组中,所以需要开辟数组int* tmp = (int*)malloc(sizeof(int) * sz);assert(tmp);//防止主函数递归导致每次都会重新开辟空间,所以使用子函数_MergeSort(data, tmp, 0, sz - 1);free(tmp);
}

非递归版本

在归并排序中,不使用递归版本时,需要考虑如何对数据进行分堆以及区间的控制,基本思路如下:在循环中,排序间隔为gap的部分数值,再改变gap值,重复前面的步骤,直到最后排序完成。具体思路如下:

//以下面的数组为例
int data[] = { 10,5,6,9,1,3,4,7 };

//归并排序非递归版本
void MergeSort_NotRecursion(int* data, int sz)
{//因为需要将排序好的数据重新拷贝到原来的数组中,所以需要开辟数组int* tmp = (int*)malloc(sizeof(int) * sz);assert(tmp);//开始间隔为1int gap = 1;while (gap < sz){//注意i每一次更新为两倍的gap,因为gap只是代表一组有多少个数据,需要i找到下一组for (int i = 0; i < sz; i += 2 * gap){int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int j = begin1;while (begin1 <= end1 && begin2 <= end2){if (data[begin1] < data[begin2]) {tmp[j++] = data[begin1++];}else{tmp[j++] = data[begin2++];}}while (begin1 <= end1){tmp[j++] = data[begin1++];}while (begin2 <= end2){tmp[j++] = data[begin2++];}}memcpy(data, tmp, sizeof(int) * sz);gap *= 2;}free(tmp);
}

非递归版本的问题

但是上面的方法存在一个问题,如果数组的数据不是2的次方个,那么将无法完成排序,存在越界问题

//下面是当数组数据为9个时的越界情况
[0, 0] [1 1]
[2, 2] [3 3]
[4, 4] [5 5]
[6, 6] [7 7]
[8, 8] [9 9][0, 1] [2 3]
[4, 5] [6 7]
[8, 9] [10 11][0, 3] [4 7]
[8, 11] [12 15][0, 7] [8 15]

越界的情况分为三种:

  1. end1begin2end2越界,例如[8, 11]、[12, 15]
  2. begin2end2越界,例如[10, 11]
  3. end2越界,例如[8, 15]

对于上面的问题可以考虑对边界进行修正

第一种解决方法:

  1. begin2end1越界时,跳出循环不进行后方数据的调整
  2. end2越界时,修正end2为数组最后一个元素的位置

//归并排序非递归版本
void MergeSort_NotRecursion(int* data, int sz)
{//因为需要将排序好的数据重新拷贝到原来的数组中,所以需要开辟数组int* tmp = (int*)malloc(sizeof(int) * sz);assert(tmp);//开始间隔为1int gap = 1;while (gap < sz){    //注意i每一次更新为两倍的gap,因为gap只是代表一组有多少个数据,需要i找到下一组for (int i = 0; i < sz; i += 2 * gap){int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int j = begin1;if (begin2 >= sz || end1 >= sz){break;}if (end2 >= sz){end2 = sz - 1;}while (begin1 <= end1 && begin2 <= end2){if (data[begin1] < data[begin2]) {tmp[j++] = data[begin1++];}else{tmp[j++] = data[begin2++];}}while (begin1 <= end1){tmp[j++] = data[begin1++];}while (begin2 <= end2){tmp[j++] = data[begin2++];}memcpy(data + i, tmp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}free(tmp);
}

第二种解决方法:

直接对所有区间进行修正,将越界的区间修正成左区间大于右区间的不存在区间,此时不存在的区间将不会进入循环,而存在的区间也是有效区间,直接整体拷贝即可

void MergeSort_NotRecursion1(int* data, int sz)
{int* tmp = (int*)malloc(sizeof(int) * sz);assert(tmp);int gap = 1;while (gap < sz){for (int i = 0; i < sz; i += 2*gap){int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int j = i;//1. end1 begin2 end2越界if (end1 >= sz){end1 = sz - 1;//修正的不存在区间begin2 = sz;end2 = sz - 1;}else if (begin2 >= sz)//2. begin2 end2 越界{//修正的不存在区间begin2 = sz;end2 = sz - 1;}else if(end2 >= sz)//3. end2越界{end2 = sz - 1;}while (begin1 <= end1 && begin2 <= end2){if (data[begin1] <= data[begin2])//当使用<=时防止出现相等时进行交换,使得排序稳定{tmp[j++] = data[begin1++];}else{tmp[j++] = data[begin2++];}}while (begin1 <= end1){tmp[j++] = data[begin1++];}while (begin2 <= end2){tmp[j++] = data[begin2++];}}memcpy(data, tmp, sizeof(int) * sz);gap *= 2;}free(tmp);
}

归并排序小优化

如果数据的个数特别大时,再让数据一直递归到只有一个数据的一层时会导致递归太深从而栈溢出,可以考虑在只有十个数据递归时采用其他排序算法进行优化,此处可以采用直接插入排序,因为每进行一次递归,数据会被分成两部分,所以当递归到只有十个数据时时,数据个数就已经比较小了

💡

这个优化只是在一定程度上有节省,当数据量特别大时,消耗和递归基本上一致

void InsertSort(int* data, int sz)
{for (int i = 1; i < sz; i++){int tmp = data[i];int end = i - 1;while (end > 0){if (data[end] > tmp){data[end + 1] = data[end];end--;}else{break;}}data[end + 1] = tmp;}
}//归并排序递归版本优化
void _MergeSort_modified(int* data, int* tmp, int left, int right)
{//确定递归结束条件if (left == right){return;}//小区间优化——直接插入排序if ((left - right + 1) < 10){InsertSort(data, left - right + 1);}//分割数组,首先确定当前数组的中间位置int mid = (left + right) / 2;_MergeSort_modified(data, tmp, left, mid);_MergeSort_modified(data, tmp, mid + 1, right);//取小的数值尾插到tmp数组中int begin1 = left;int end1 = mid;int begin2 = mid + 1;int end2 = right;int i = left;while (begin1 <= end1 && begin2 <= end2){if (data[begin1] < data[begin2]){tmp[i++] = data[begin1++];}else{tmp[i++] = data[begin2++];}}//存在一个数组先走完的情况while (begin1 <= end1){tmp[i++] = data[begin1++];}while (begin2 <= end2){tmp[i++] = data[begin2++];}//排序完之后将tmp数组中的数据拷贝回原来的数组memcpy(data + left, tmp + left, sizeof(int) * (right - left + 1));
}//归并排序递归版
void MergeSort_modified(int* data, int sz)
{//因为需要将排序好的数据重新拷贝到原来的数组中,所以需要开辟数组int* tmp = (int*)malloc(sizeof(int) * sz);assert(tmp);//防止主函数递归导致每次都会重新开辟空间,所以使用子函数_MergeSort_modified(data, tmp, 0, sz - 1);free(tmp);
}

归并排序的时间复杂度是O(Nlog_{2}{N}),空间复杂度为O(N),归并排序时稳定排序算法

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微服务:Sentinel篇

1. 初识Sentinel 1.1. 雪崩问题以及解决方案 1.1.1. 雪崩问题 微服务中&#xff0c;服务间调用关系错综复杂&#xff0c;一个微服务往往依赖于多个其它微服务。 如图&#xff0c;如果服务提供者I发生了故障&#xff0c;当前的应用的部分业务因为依赖于服务I&#xff0c;因此…

Github 2024-03-17 php开源项目日报 Top9

根据Github Trendings的统计,今日(2024-03-17统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目9Blade项目2Laravel:表达力和优雅的 Web 应用程序框架 创建周期:4631 天开发语言:PHP, BladeStar数量:75969 个Fork数量:24281 次关…

Java关于物联网消息引擎:EMQ X

1.背景 1、5G 时代&#xff0c;万物互联 随着5G的到来&#xff0c;万物互联已经成为现实&#xff0c;物联网行业得以蓬勃发展&#xff0c;催生了很多的应用&#xff0c;比如&#xff1a;物联网pass平台&#xff0c;车联网&#xff0c;面向云平台的IOT-Hub&#xff0c;NB-IoT蜂…

【算法训练营】最近点对,纸牌,青蛙(Python实现)

最近点对 描述 给定n个二维平面上的点&#xff0c;求距离最近的一对点&#xff0c;输出他们的距离。 输入 第一行包含一个正整数n。 接下来n行&#xff0c;每行包含两个整数x,y&#xff0c;表示一个点的坐标。 输出 输出距离最近的一对点的距离&#xff0c;保留两位小数。 样例…

阿里云-零基础入门推荐系统 【特征工程】

文章目录 赛题介绍评价方式理解赛题理解制作特征和标签&#xff0c; 转成监督学习问题导包df节省内存函数训练和验证集的划分获取历史点击和最后一次点击读取训练、验证及测试集读取召回列表读取各种Embedding读取文章信息读取数据对训练数据做负采样将召回数据转换成字典制作与…

194 基于matlab的日历GUI制作

基于matlab的日历GUI制作&#xff0c;可实时显示当前的日期和时间&#xff0c;精确到秒。非常漂亮&#xff0c;也很基础&#xff0c;学习GUI的不错程序&#xff0c;程序已调通&#xff0c;可直接运行。 194 matlab 日历制作 GUI可视化 - 小红书 (xiaohongshu.com)

JVM学习-垃圾回收专题

目录 1.如何判断对象可以回收 1.1引用计数法 1.2可达性分析算法 1.3五种引用 1.4拓展&#xff1a;直接内存 2.垃圾回收算法 2.1标记清除算法 2.2标记整理算法 2.3复制 3.分代垃圾回收 3.垃圾回收器 3.1串行垃圾回收器 3.2吞吐量优先垃圾回收器 3.3响应时间优先垃圾回收器…

Microsoft Word 符号 / 特殊符号

Microsoft Word 符号 / 特殊符号 1. 插入 -> 符号 -> 其他符号 -> Wingdings 2References 1. 插入 -> 符号 -> 其他符号 -> Wingdings 2 ​ References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《考虑抽水蓄能电站参与容量交易辅助服务的双层优化策略》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

c++算法学习笔记 (8) 树与图部分

1.树与图的存储 &#xff08;1&#xff09;邻接矩阵 &#xff08;2&#xff09;邻接表 // 链式前向星模板&#xff08;数组模拟&#xff09; #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N 100010, M …

npm下载慢换国内镜像地址

1 设置淘宝镜像地址 npm config set registry http://registry.npm.taobao.org 2 查看当前下载地址 npm config get registry 3 其它镜像地址列表&#xff1a; 1. 官方镜像&#xff1a;https://registry.npmjs.org/ 2. 淘宝镜像&#xff1a;https://registry.npm.taobao.o…

量子磁场测量“碰上”脑科学,未磁科技无创脑功能成像系统研发成功

从微观的神经元活动到宏观的认知行为&#xff0c;脑科学探索着人类最神秘、最复杂的领域之一。在这个过程中&#xff0c;科研人员、医生和创业者们扮演着至关重要的角色。他们不仅致力于揭示大脑的奥秘&#xff0c;更将科研成果转化为实际应用&#xff0c;推动脑科学领域的进步…