八大算法排序@归并排序(C语言版本)

目录

  • 归并排序
    • 概念
    • 算法思想
      • 第一步
      • 第二步
      • 第三步
    • 算法步骤
    • 代码实现
      • 代码1
      • 代码优化
    • 时间复杂度
    • 空间复杂度
    • 特性总结

归并排序

概念

  归并排序(Merge Sort)是一种基于分治策略的经典排序算法。它的基本思想是将待排序的数组划分成两个子数组,分别对这两个子数组进行递归排序,然后将已排序的子数组合并成一个有序的数组。归并排序的关键在于合并操作,这是该算法的核心。



算法思想

  归并、归并,其实可以认为就是递归+合并。递归就是将待排序的数组通过递归,细分到子数组有序为止。最差的情况,如细分到数组只剩一个元素,那么该数组既可以认为是升序的,也可以认为是降序的,总而言之,是有序的。
然后将一个个有序的数组,进行合并,最终合并成一个有序的数组。因此该排序算法的核心便是合并算法

我们借助数组arr = { 6 , 4 , 3 , 2 , 5 , 8 , 1 , 7 }。借用图形模拟演示下流程。

流程图

通过上图所示的流程图,或许看着比较通俗易懂,然后实际上用代码实现起来还是没有想象中的那么简单的。




第一步

首先,我们不可能如流程图演示一样,递归一次就开辟一些新的数组,而且频繁的开辟数组也会造成性能的浪费。因此,在一开始便会申请一块与待排序数组同样空间大小的临时数组tmp。

// 归并排序 - 递归实现
void MergeSort(int* a, int n)
{assert(a);	// 确保数组不为空int* tmp = (int*)malloc(sizeof(int) * n);free(tmp);	// 申请的空间,没用时要主动释放
}

解决了临时空间的问题,下一步我们将着手解决递归和合并的问题。




第二步

因为待排序的数据与后序递归细分到有序数组都是一样的问题,我们可以统一给它们划分成一个子问题,如以下的_MergeSort()函数:


// 归并排序 - 递归实现
void MergeSort(int* a, int n)
{assert(a);int* tmp = (int*)malloc(sizeof(int) * n);_MergeSort(a, 0, n - 1, tmp);	// 子问题,解决递归和合并的问题free(tmp);}

因为递归划分数组时,是根据数组下标进行划分的,因此子函数设计时,传入数组下标的范围更佳,同时要将临时数组tmp也传过去。




第三步

  如下,对数组进行划分,分别用left 和 right 接收传入的数组下标的范围,然后通过下标算出数组的中间下标值,用 变量mid接收,根据变量mid,将数组划分为两个区间,区间范围为:[ left , mid ] 、 [ mid+1 , right ] 。
而对于[ left , mid ] 和 [ mid+1 , right ] 两个子数组若是有序,则可以进行合并;如果还没有序时,依旧是子问题,这便是递归的由来。
  子函数_MergeSort(),传入的参数依旧是待排序数组的下标范围,和临时辅助的数组tmp。如下代码所示:

// 时间复杂度:O(N*logN)
// 空间复杂度:O(N)
void _MergeSort(int* a, int left, int right, int* tmp)
{int mid = (left + right) / 2;// 分割为两个区间[left,mid]   [mid+1,right]//[left,mid] [mid+1,right] 有序,则可以合并,他们还没有序时,子问题解决_MergeSort(a, left, mid, tmp);_MergeSort(a, mid + 1, right, tmp);
}

观察以上的代码,我们发现,
1、递归函数中,缺少了结束条件,这将导致一直递归个不停,从而导致栈溢出,致使程序崩溃。而如何确定结束条件呢?回顾流程图,当数组中只有一个元素时,便可以认为数组是有序的了,即当待排序数组的下标范围, left >= right 时便可以结束递归,返回,进行合并了。
2、缺少合并的步骤。

因此,要解决以上两个问题,如下:

// 时间复杂度:O(N*logN)
// 空间复杂度:O(N)
void _MergeSort(int* a, int left, int right, int* tmp)
{if (left >= right)return;int mid = (left + right) / 2;// 分割为两个区间[left,mid]   [mid+1,right]//[left,mid] [mid+1,right] 有序,则可以合并,他们还没有序时,子问题解决_MergeSort(a, left, mid, tmp);_MergeSort(a, mid + 1, right, tmp);/*  当执行到这里时,数组[left,mid] 和 [mid+1 , right] 已经有序,因此下面将是退出递归、合并数组的步骤 */// 归并:递归往回退	([left,mid]、[mid+1,right]两个区间已经有序)int begin1 = left, end1 = mid;int begin2 = mid+1, end2 = right;int index = begin1;		// 此处注意,tmp起始位置在 leftwhile (begin1 <= end1 && begin2 <= end2){// 在两个数组中,依次找最小的数存入临时数组tmpif (a[begin1] < a[begin2])tmp[index++] = a[begin1++];elsetmp[index++] = a[begin2++];}// 一组数组归并完,将另一组数组剩下的全部归并到后面,结束的那一组将不会进入while循环while (begin1 <= end1)tmp[index++] = a[begin1++];while (begin2 <= end2)tmp[index++] = a[begin2++];// 把归并好的在tmp的数据,再拷贝回到原数组for (int i = left; i <= right; i++)a[i] = tmp[i];}

以上,需要注意的是:
1、当待排序的数组还未有序时,统一归纳为子问题,继续递归下去。直到待排序数组有序时(数组只有一个元素)才开始递归返回,接着执行数组的合并。
2、需要将待合并的两个数组,挨个选取两个数组中最小(升序)/最大(降序)的数放入临时数组tmp中。同时需要注意,临时数组tmp的下标问题。
3、将两个待合并的数组,有序的合并到临时数组tmp,返回上一级递归前,需要将临时数组中合并好的、排好序数组,拷贝回原数组。


以上便是对于归并算法的大体流程,下面是对于该算法的步骤大体总结。



算法步骤

1、分割数组: 将待排序的数组划分为两个相等(或近似相等)大小的子数组。这一步采用分治策略,递归地对子数组进行分割,直到每个子数组包含一个元素。

2、递归排序: 对分割后的子数组进行递归排序。这是通过再次调用归并排序来实现的。

3、合并操作: 将已排序的子数组合并成一个有序数组。合并操作是归并排序的关键步骤,它涉及比较已排序的子数组的元素,并按顺序将它们合并到一个新的数组中。


结合以上的全部学习,让我们给出完整的代码,进行学习上的整合。



代码实现

代码1


// 时间复杂度:O(N*logN)
// 空间复杂度:O(N)
void _MergeSort(int* a, int left, int right, int* tmp)
{if (left >= right)return;int mid = (left + right) / 2;// 分割为两个区间[left,mid]   [mid+1,right]//[left,mid] [mid+1,right] 有序,则可以合并,他们还没有序时,子问题解决_MergeSort(a, left, mid, tmp);_MergeSort(a, mid + 1, right, tmp);/*  分解  +   合并  */// 归并:递归往回退	([left,mid]、[mid+1,right]两个区间已经有序)int begin1 = left, end1 = mid;int begin2 = mid+1, end2 = right;int index = begin1;		// 此处注意,tmp起始位置在 leftwhile (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2])tmp[index++] = a[begin1++];elsetmp[index++] = a[begin2++];}// 一组数组归并完,将另一组数组剩下的全部归并到后面,结束的那一组将不会进入while循环while (begin1 <= end1)tmp[index++] = a[begin1++];while (begin2 <= end2)tmp[index++] = a[begin2++];// 把归并好的在tmp的数据,再拷贝回到原数组for (int i = left; i <= right; i++)a[i] = tmp[i];}// 归并排序 - 递归实现
void MergeSort(int* a, int n)
{assert(a);int* tmp = (int*)malloc(sizeof(int) * n);_MergeSort(a, 0, n - 1, tmp);free(tmp);}

以上便是对于归并算法的具体代码实现。其中,为了更好的函数封装性。我们可以将具体的合并过程,封装成一个合并函数,使代码可读性更强。如下:




代码优化

//  合并处理函数
void MergeArr(int* a, int begin1, int end1, int begin2, int end2, int* tmp)
{int left = begin1, right = end2;int index = begin1;		// 此处注意,tmp起始位置在 leftwhile (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2])tmp[index++] = a[begin1++];elsetmp[index++] = a[begin2++];}// 一组数组归并完,将另一组数组剩下的全部归并到后面,结束的那一组将不会进入while循环while (begin1 <= end1)tmp[index++] = a[begin1++];while (begin2 <= end2)tmp[index++] = a[begin2++];// 把归并好的在tmp的数据,再拷贝回到原数组for (int i = left; i <= right; i++)a[i] = tmp[i];
}// 时间复杂度:O(N*logN)
// 空间复杂度:O(N)
void _MergeSort(int* a, int left, int right, int* tmp)
{if (left >= right)return;int mid = (left + right) / 2;// 分割为两个区间[left,mid]   [mid+1,right]//[left,mid] [mid+1,right] 有序,则可以合并,他们还没有序时,子问题解决_MergeSort(a, left, mid, tmp);_MergeSort(a, mid + 1, right, tmp);/*  分解  +   合并  */// 归并:递归往回退	([left,mid]、[mid+1,right]两个区间已经有序)MergeArr(a, left, mid, mid + 1, right, tmp);}// 归并排序 - 递归实现
void MergeSort(int* a, int n)
{assert(a);int* tmp = (int*)malloc(sizeof(int) * n);_MergeSort(a, 0, n - 1, tmp);free(tmp);}

以上便是封装性更佳的归并算法。



时间复杂度

O(N*logN)

归并排序有点类似于二叉树中的后序遍历。先将数组平分、平分,直到最后不能再分时,再合并返回。
因为递归的高度为logN,而合并的过过程,每一层可以归纳统计认为是N。
因此归并排序的时间复杂度为:O(N*logN)。



空间复杂度

O(N)
该算法需要用到额外开辟的数组。数组大小为待排序数组的大小。故空间复杂度为O(N)。
(N为待排序数组的个数)




特性总结

1、 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问
题。
2、时间复杂度:O(N*logN)
3、空间复杂度:O(N)
4、稳定性:稳定

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

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

相关文章

Ubuntu 实时查看显存调用命令 free 及命令详解与原理说明(全)

Ubuntu 实时查看显存调用命令 free 及详解 文章目录 Ubuntu 实时查看显存调用命令 free 及详解1 free 作用1.1 语法&#xff1a;1.2 单独显示例子1.3 组合显示例子 2 输出介绍3 原理解释3.1 buff / cache&#xff08;即 buffer / cache&#xff09;3.1.1 buffer 缓冲区3.1.2 ca…

FineReport--学习笔记

1 项目介绍 1.1 项目背景 某互联网电商公司拥有超过50万门店用户和8000店铺用户&#xff0c;店铺主要以卖家身份进行销售&#xff0c;门店以买家身份进行购买&#xff0c;每天会产生许多销售订单。根据订单信息以及其他的门店信息&#xff0c;店铺信息&#xff0c;商品信息等…

用友NC word.docx 任意文件读取漏洞复现

0x01 产品简介 用友NC是一款企业级ERP软件。作为一种信息化管理工具,用友NC提供了一系列业务管理模块,包括财务会计、采购管理、销售管理、物料管理、生产计划和人力资源管理等,帮助企业实现数字化转型和高效管理。 0x02 漏洞概述 用友NC 系统word.docx等接口存在任意文件…

rabbitmq延时队列相关配置

确保 RabbitMQ 的延时消息插件已经安装和启用。你可以通过执行以下命令来安装该插件&#xff1a; rabbitmq-plugins enable rabbitmq_delayed_message_exchange 如果提示未安装&#xff0c;以下是安装流程&#xff1a; 查看mq版本&#xff1a; 查看自己使用的 MQ&#xff08;…

CHS_01.2.1.1+2.1.3+进程的概念、组成、特征

CHS_01.2.1.12.1.3进程的概念、组成、特征 进程进程的概念 进程的组成——PCB进程的组成——PCB进程的组成——程序段、数据段知识滚雪球&#xff1a;程序是如何运行的&#xff1f;进程的组成进程的特征 知识回顾与重要考点 从这个小节开始 我们会正式进入第二章处理机管理相关…

【云计算】云计算概述

1. 云计算概述 1.1 云计算的定义 美国国家标准与技术研究院(NIST)定义 云计算是一种按使用量付费的模式&#xff0c;这种模式提供可用的、便捷的、按需的网络访问&#xff0c;进入可配置的计算资源共享池(资源包括网络&#xff0c;服务器&#xff0c;存储&#xff0c;应用软件…

分享两个概念:非受检异常和受检异常

分享两个概念&#xff1a;非受检异常和受检异常 愿你的每一天都充满阳光和笑声&#xff0c;愿每一步都是轻松与愉快。在新的旅程中&#xff0c;愿你找到勇气攀登高峰&#xff0c;找到智慧化解困境。 愿你的心中充满温暖和善意&#xff0c;愿你的梦想如彩虹般美丽且真实。愿你发…

TCN 时序卷积网络 (temporal convolutional network)【因果卷积、空洞卷积】

文章目录 TCN 时序卷积 &#xff08;temporal convolutional network&#xff09;1.因果卷积2.膨胀卷积 TCN 时序卷积 &#xff08;temporal convolutional network&#xff09; 它由膨胀卷积核因果卷积两种卷积构成。 如图&#xff1a;左边是膨胀因果卷积&#xff0c;右边是…

206. 反转链表(Java)

题目描述&#xff1a; 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 输入&#xff1a; head [1,2,3,4,5] 输出&#xff1a; [5,4,3,2,1] 代码实现&#xff1a; 1.根据题意创建一个结点类&#xff1a; public class ListNode {int val…

048.Python包和模块_发布包和模块

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

ARM工控机Node-red使用教程

嵌入式ARM工控机Node-red安装教程 从前车马很慢书信很远&#xff0c;而现在人们不停探索“科技改变生活”。 智能终端的出现改变了我们的生活方式&#xff0c;钡铼技术嵌入式工控机协助您灵活布建能源管理、大楼自动化、工业自动化、电动车充电站等各种多元性IoT应用&#xff…

Unity组件开发--UI管理器

1.Canvas组件&#xff1a; 注意属性&#xff1a; &#xff08;1&#xff09;渲染模式是&#xff1a;屏幕空间相机 &#xff08;2&#xff09;创建一个UICamera节点&#xff0c;管理相机 &#xff08;3&#xff09;屏幕画布缩放模式 &#xff08;4&#xff09;画布下挂载两…

前端项目构建打包生成Git信息文件

系列文章目录 TypeScript 从入门到进阶专栏 文章目录 系列文章目录前言一、前端项目构建打包生成Git信息文件作用二、步骤1.引入相关的npm包1.1. **fs** 包1.2. **child_process** 包1.3. **os** 包 (非必须 如果你想生成的文件信息中包含当前电脑信息则可用)1.4. **path** 包…

基于宝塔搭建Discuz!论坛

一、安装宝塔 我是在我的虚拟机上安装图的宝塔 虚拟机版本&#xff1a;Ubuntu 18.04 wget -O install.sh https://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh 6dca892c安装完成之后在浏览器输入你的地址 https://你的域名&#xff08;或…

每天刷两道题——第十一天

1.1滑动窗口最大值 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值 。 输入&#xff1a;nums [1,3,-1,-3,5,3,6,7], k 3 输出&…

面试题-DAG 有向无环图

有向无环图用于解决前后依赖问题&#xff0c;在Apollo中用于各个组件的依赖管理。 在算法面试中&#xff0c;有很多相关题目 比如排课问题&#xff0c;有先修课比如启动问题&#xff0c;需要先启动1&#xff0c;才能启动2 概念 顶点&#xff1a; 图中的一个点&#xff0c;比…

k8s 之7大CNI 网络插件

一、介绍 网络架构是Kubernetes中较为复杂、让很多用户头疼的方面之一。Kubernetes网络模型本身对某些特定的网络功能有一定要求&#xff0c;但在实现方面也具有一定的灵活性。因此&#xff0c;业界已有不少不同的网络方案&#xff0c;来满足特定的环境和要求。 CNI意为容器网络…

C# Entity Framework 中不同的数据的加载方式

延迟加载 延迟加载是指在访问导航属性时&#xff0c;Entity Framework 会自动查询数据库并加载相关数据。这种方式在我们需要访问导航属性时比较方便&#xff0c;因为我们无需手动加载相关数据&#xff0c;而且只会在需要时才会进行查询&#xff0c;从而减少了不必要的开销。但…

模仿Activiti工作流自动建表机制,实现Springboot项目启动后自动创建多表关联的数据库与表的方案

文/朱季谦 熬夜写完&#xff0c;尚有不足&#xff0c;但仍在努力学习与总结中&#xff0c;而您的点赞与关注&#xff0c;是对我最大的鼓励&#xff01; 在一些本地化项目开发当中&#xff0c;存在这样一种需求&#xff0c;即开发完成的项目&#xff0c;在第一次部署启动时&…

C++笔记之cout高亮输出以及纯C++实现一个彩色时钟

C笔记之cout高亮输出以及纯C实现一个彩色时钟 code review! 文章目录 C笔记之cout高亮输出以及纯C实现一个彩色时钟一.cout高亮输出1.1.运行1.2.代码一1.3.代码二1.4.重置终端的文本格式到默认设置说明 二.纯C实现一个彩色时钟2.1.运行2.2.main.cc2.3.cout带颜色打印输出技巧…