八大算法排序@归并排序(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.mzph.cn/news/603146.shtml

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

相关文章

17.Linux Shell输入输出流管理

文章目录 Linux Shell输入输出流管理1)标准文件描述符2)脚本中重定向输出临时重定向永久重定向自定义输出重定向 3)重定向输入4)支持读写的文件描述符5)关闭文件描述符6) 列出打开的文件描述符及关闭文件描述符7)输出同时发送到显示器和日志文件 欢迎访问个人网络日志&#x1f…

大模型实战笔记02——大模型demo

大模型实战笔记02——大模型demo 1、大模型及InternLM模型介绍 2、InternLM-Chat-7B智能对话Demo 3、Lagent智能体工具调用Demo 4、浦语灵笔图文创作理解Demo 5、通用环境配置 注 笔记图片均为视频截图 笔记课程视频地址&#xff1a;https://www.bilibili.com/video/BV1Ci4y1…

基于 IP 多播的网络会议程序(2024)

1.题目描述 局域网 IP 多播程序&#xff0c;设计一个图形界面的网络会议程序&#xff08;实现文本多播方式即可&#xff09;。 2.演示Demo 3.参考代码 广播发送代码 //服务端 #include <winsock2.h> #include <iostream> #include <list>#pragma comment(l…

顶顶通呼叫中心中间件通过队列外呼拨打另一个sip并且放音(mod_cti基于FreeSWITCH)

介绍 顶顶通呼叫中心中间件通过队列外呼拨打另一个sip并且放音 一、添加acl 打开ccadmin->点击配置文件->点击acl.conf->在</list>后面添加一条图中的信息->muqi是我自己设置的名字你们可以修改为自己需要的名字->添加好了点击提交XML->在运维调试点…

如何理解链接(Linking)这一编译过程中的步骤

在理解链接&#xff08;Linking&#xff09;这一编译过程中的步骤之前&#xff0c;有必要了解编译器的整体工作流程。编译器通常经历以下几个阶段&#xff1a; 预处理&#xff08;Preprocessing&#xff09;&#xff1a;处理源代码文件中的预处理指令&#xff0c;如 #include 指…

Redis概览

Redis存储是Key-Value结构的数据&#xff0c;其中Key是字符串类型&#xff0c;Value有5种常见的数据类型 字符串 String 哈希 hash 列表 list 集合 set 有序集合 sorted set / zset 各种数据类型的特性 字符串操作命令 : ● SET ke…

v8 pwn利用合集

文章目录 前置知识JS Object 相关Ignition 相关JIT - turboFan 相关starCTF2019 OOB【越界读写map字段】googleCTF2018 jit【浮点数精度丢失导致越界读写】数字经济线下 Browser【Object::toNumber中callback导致的越界写】前置知识 JS Object 相关 V8 中的对象表示 ==> 基…

LeetCode 2125. 银行中的激光束数量【数组,遍历】1280

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

深入理解 Vue.js 中的 `h` 函数:虚拟 DOM 创建指南

Vue.js 是一个用于构建用户界面和单页应用程序的渐进式 JavaScript 框架。它的核心概念之一是虚拟 DOM&#xff0c;这是实际 DOM 的轻量级副本&#xff0c;Vue 使用它来优化对网页的更新。为了操作虚拟 DOM&#xff0c;Vue 提供了一个通常被称为 h 函数的方法。这个函数对于理解…

大模型查询工具助手之股票免费查询接口

新浪股票免费查询接口 股票研究的实践中需要查询股票市场接口&#xff0c;百度搜索大多链接都要收费或者注册。 记得新浪股票以前是免费查询&#xff0c;但现在遇到了小问题。 决策引擎专栏&#xff1a; Falcon构建轻量级的REST API服务 决策引擎-利用Drools实现简单防火墙策…

vue 用 h() 函数创建 Vnodes

目录 前言一、h() 函数的基本使用方式二、h() 函数的进阶使用方式1、条件渲染2、列表渲染3、事件4、使用插槽 前言 Vue 提供了一个 h() 函数用于创建 vnodes。 h() 是 hyperscript 的简称——意思是“能生成 HTML (超文本标记语言) 的 JavaScript”。 const vnode h(div, /…

技术学习周刊第 1 期

2018 年参与过 1 年的 ARTS 打卡&#xff0c;也因为打卡有幸加入了 MegaEase 能与皓哥&#xff08;左耳朵耗子&#xff09;共事。时过境迁&#xff0c;皓哥已经不在了&#xff0c;自己的学习梳理习惯也荒废了一段时间。 2024 年没给自己定具体的目标&#xff0c;只要求自己好好…

电话号码信息收集工具:PhoneInfoga | 开源日报 No.137

sundowndev/phoneinfoga Stars: 11.2k License: GPL-3.0 PhoneInfoga 是一个用于扫描国际电话号码的信息收集框架&#xff0c;它允许用户首先收集基本信息 (如国家、地区、运营商和线路类型)&#xff0c;然后使用各种技术来尝试找到 VoIP 提供商或识别所有者。该工具与一系列必…

[足式机器人]Part2 Dr. CAN学习笔记-动态系统建模与分析 Ch02-7二阶系统

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-动态系统建模与分析 Ch02-7二阶系统 1. 二阶系统对初始条件的动态响应 Matlab/Simulink - 2nd Order Syetem Response to IC2. 二阶系统的单位阶跃响应 2nd Order System Unit Step Response3. 二…

copilot在pycharm的应用

Copilot在PyCharm中的应用 一、引言 随着人工智能技术的飞速发展&#xff0c;AI在编程领域的应用也越来越广泛。Copilot&#xff0c;作为一款由微软开发的AI编程助手&#xff0c;已经引起了广大开发者的关注。它利用深度学习技术&#xff0c;通过分析大量开源代码&#xff0c…

Linux-v4l2框架

框架图 从上图不难看出&#xff0c;v4l2_device作为顶层管理者&#xff0c;一方面通过嵌入到一个video_device中&#xff0c;暴露video设备节点给用户空间进行控制&#xff1b;另一方面&#xff0c;video_device内部会创建一个media_entity作为在media controller中的抽象体&a…

unity中0GC优化方案《zstring》

文章目录 序言简介GC带来的问题性能瓶颈玩家体验受损 使用方式 序言 游戏开发秉承遇到好东西要分享&#xff0c;下面介绍zstring&#xff0c;感谢作者开源无私奉献 源码地址&#xff1a;https://github.com/871041532/zstring 简介 GC带来的问题 性能瓶颈 GC暂停主线程执行…

LaTeX中常用的字母及符号

收集 LaTeX 中常用的字母、符号。 字体字形设置 命令实例说明\fbox LaTeX \fbox{LaTeX} LaTeX​加边框\boxed L a T e X \boxed{LaTeX} LaTeX​斜体加边框\mathbf L a T e X \mathbf{LaTeX} LaTeX加粗\boldsymbol L a T e X \boldsymbol{LaTeX} LaTeX斜体加粗 希腊字母 符号…

docker 安装elasticsearch、kibana、cerebro

安装步骤 第一步安装 docker 第二步 拉取elasticsearch、kibana、cerebro 镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.10.2 docker pull docker.elastic.co/kibana/kibana:7.10.2 docker pull lmenezes/cerebro:latest第三步、创建 容器 创建e…

设计模式之中介者模式【行为型模式】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档> 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某…