当前位置: 首页 > news >正文

C语言实现归并排序(Merge Sort)

目录

一、递归实现归并排序

1. 归并排序的基本步骤

2.动图演示

3.基本思路 

4.代码

 二、非递归实现

1.部分代码

2.代码分析

修正后代码:

 归并过程打印

性能分析

 复杂度分析


归并排序是一种高效的排序算法,采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序的基本思想是将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

一、递归实现归并排序

1. 归并排序的基本步骤

  1. 分解:将数组分解成两个较小的子数组,直到子数组的大小为1。
  2. 递归进行排序并合并:递归地对子数组进行排序,并将已排序的子数组合并成一个大的有序数组,直到合并为1个完整的数组。

2.动图演示

3.基本思路 

归并排序是采用分治法的一个非常典型的应用。其基本思想是:将已有序的子序合并,从而得到完全有序的序列,即先使每个子序有序,再使子序列段间有序。

那么如何得到有序的子序列呢?当序列分解到只有一个元素或是没有元素时,就可以认为是有序了,这时分解就结束了,开始合并。

4.代码

void _MergSort(int* a, int left,int right,int* tmp)
{if (left >= right)return;int mid = left + (right - left) / 2;_MergSort(a, left, mid, tmp);_MergSort(a, mid+1, right, tmp);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int i = left;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while (begin1<=end1){tmp[i++] = a[begin1++];}while (begin2<=end2){tmp[i++] = a[begin2++];}for (int j = 0; j <= right; j++){a[j] = tmp[j];}
}void MergSort(int* a, int n)
{int* tmp = (int*)malloc(n * sizeof(int));if (tmp == NULL){perror("malloc fail");exit(-1);}_MergSort(a,0, n-1, tmp);free(tmp);
}

 二、非递归实现

归并排序的非递归算法并不需要借助栈来完成,我们只需要控制每次参与合并的元素个数即可,最终便能使序列变为有序:

1.部分代码

void MergSortNonF(int* a, int n)
{int* tmp = (int*)malloc(n * sizeof(int));if (tmp == NULL){perror("malloc fail");exit(-1);}int gap = 1;while (gap < n){for (int j = 0; j < n; j += 2 * gap){//*****注意边界处理*******int begin1 = j, end1 = j + gap - 1;int begin2 = j + gap, end2 = j + gap * 2 - 1;printf("[%d %d] [%d %d]", begin1, end1, begin2, end2);int i = j;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}}printf("\n");memcpy(a, tmp, n * sizeof(int));gap *= 2;}free(tmp);tmp = NULL;
}

2.代码分析

这是一个有问题的代码,当我们存放的数据是2的次方时,可以正常排序,为了便于观察,我们把它的下标给打印下来

如果我们存放的数据不是2的次方个就会出现一些越界。


情况一:第二组部分越界
 当最后一个小组进行合并时,第二个小区间存在,但是该区间元素个数不够gap个,这时我们需要在合并序列时,对第二个小区间的边界进行控制。

情况二:第二组全部越界
 当最后一个小组进行合并时,第二个小区间不存在,此时便不需要对该小组进行合并。

情况三:第一组end1越界
 当最后一个小组进行合并时,第二个小区间不存在,并且第一个小区间的元素个数不够gap个,此时也不需要对该小组进行合并。(可与情况二归为一类)

只要把控好这三种特殊情况,写出归并排序的非递归算法便轻而易举了。

修正后代码:

void MergSortNonF(int* a, int n)
{int* tmp = (int*)malloc(n * sizeof(int));if (tmp == NULL){perror("malloc fail");exit(-1);}int gap = 1;while (gap < n){for (int j = 0; j < n; j += 2 * gap){//*****注意边界处理*******int begin1 = j, end1 = j + gap - 1;int begin2 = j + gap, end2 = j + gap * 2 - 1;//第一组越界if (end1 >= n){printf("[%d %d]", begin1,n-1);break;}//第二组全部越界if (begin2 >= n){printf("[%d %d]", begin1, end1);break;}//第二组部分越界,越界之前的那一部分依然要归if (end2 >= n){//修正end2end2 = n - 1;}printf("[%d %d] [%d %d]", begin1, end1, begin2, end2);int i = j;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}//归并那一部分拷贝哪一部分memcpy(a+j, tmp+j, (end2-j+1)* sizeof(int));}printf("\n");//memcpy(a, tmp, n * sizeof(int));gap *= 2;}free(tmp);tmp = NULL;
}

 归并过程打印

性能分析

 复杂度分析

  • 时间复杂度:归并排序的时间复杂度为O(n log n),其中n是数组的长度。这主要是由于归并排序将问题分解为两个子问题(分解),递归地解决它们(递归),然后将解决方案合并(合并)。
  • 空间复杂度:归并排序的空间复杂度为O(n),其中n是数组的长度。这是因为归并排序在合并过程中需要与原数组同样大小的额外空间来存放临时数组。

归并排序是稳定的排序算法,即相等的元素在排序后的序列中仍然保持原来的顺序。这个特性在某些应用中非常重要。

 


如有错误,劳烦各位指正

相关文章:

  • oracle 定时任务每月27号到月底
  • AccessClient在MacOS14 (sonoma)闪退无法调用远程桌面
  • Spark 性能优化高频面试题及答案
  • 国产操作系统(统信UOS)网络安全等级保护基础安全加固
  • 杨辉三角-C语言
  • word中的表格全部设置宽度100%
  • 之前请求都是正常的,然后第三方的数据库抖动了导致请求的二次请求出现431
  • PHP视频活体检测API接口示例-视频活体检测引领身份验证新潮流
  • windows安装Redis以后配置远程访问
  • 项目启动错误
  • harmonyos面试题
  • Vue3 中 this 一分钟了解
  • Linux之我不会
  • 基于Memcached协议的路由器Mcrouter介绍
  • ESP32-WROOM-32 [创建AP站点-客户端-TCP透传]
  • 「译」Node.js Streams 基础
  • 【个人向】《HTTP图解》阅后小结
  • flutter的key在widget list的作用以及必要性
  • github指令
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • PhantomJS 安装
  • python学习笔记 - ThreadLocal
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • scala基础语法(二)
  • 闭包--闭包作用之保存(一)
  • 对象引论
  • 记一次和乔布斯合作最难忘的经历
  • 目录与文件属性:编写ls
  • 什么软件可以剪辑音乐?
  • 世界上最简单的无等待算法(getAndIncrement)
  • 再次简单明了总结flex布局,一看就懂...
  • 在weex里面使用chart图表
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • # Spring Cloud Alibaba Nacos_配置中心与服务发现(四)
  • # windows 安装 mysql 显示 no packages found 解决方法
  • # wps必须要登录激活才能使用吗?
  • #{} 和 ${}区别
  • (30)数组元素和与数字和的绝对差
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (poj1.3.2)1791(构造法模拟)
  • (官网安装) 基于CentOS 7安装MangoDB和MangoDB Shell
  • (四)Android布局类型(线性布局LinearLayout)
  • (文章复现)基于主从博弈的售电商多元零售套餐设计与多级市场购电策略
  • (五)网络优化与超参数选择--九五小庞
  • (心得)获取一个数二进制序列中所有的偶数位和奇数位, 分别输出二进制序列。
  • (学习日记)2024.02.29:UCOSIII第二节
  • **登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**
  • .\OBJ\test1.axf: Error: L6230W: Ignoring --entry command. Cannot find argumen 'Reset_Handler'
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .NET 中的轻量级线程安全
  • .net 逐行读取大文本文件_如何使用 Java 灵活读取 Excel 内容 ?
  • .NET(C#) Internals: as a developer, .net framework in my eyes
  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • .NET多线程执行函数
  • .net和jar包windows服务部署