MergeSort(迭代归并排序)——C语言实现

2023-05-16

前言:

归并排序跟快速排序有异曲同工之妙,都是分治法的典型代表。但是这种分治法都有不小的弊端,就是需要占用大量的系统栈,很容易造成空间的大量浪费,所以就有用迭代来优化递归的操作。这次的归并排序,我采用迭代的方式来进行编写。

思路:

归并算法如其名,需要不断地归并。我们在学习数组的时候,应该学习到过合并两个有序的数组的时间复杂度是很低的,不断地二分并进行合并,这个算法的时间复杂度仅为O(nlogn),但是合并数组的时候是需要一个额外的相当于原数组大小的空间的,所以空间复杂度并不是很好O(n)

所以最先的最先,我们要先开辟一块空间

int *b = (int*)malloc((length+5) * sizeof(int));
//加上一些偏移量以防越界,同时,要记得最后要free掉这片空间
//这个free不重视要出大问题的,接下来会说

开始写这个排序最先想到的莫非就是如何不断地二分。既然我们选择的是自下而上的进行迭代,我们一开始的1个1个的比较从而形成2个,2个比较的情况,因此我们的两层循环其实就已经都定下来了:(offset是偏移量的意思)

for (offset = 1; offset < length; offset *=2)
    for (left = 0; left < length; left += 2 * offset)

由于是二分,所以偏移量自下而上的是1,2,4,8···,也就是说 偏移量的变化是  offset *=2

然后left和接下来的right是在原数组中进行滑动的下标,用来比较的。

接下来,我们需要思考一下left 和right有哪些情况。其实,这里主要的问题就是怕越界,因此我们为了方便表示,重新定义三个变量,来处理可能越界的问题:

int low = left;
int mid = min(left + offset, length);
int high = min(left + 2 * offset, length);

(left+offset 相当于右比较框的第一个元素;left+2*offset 相当于同样长的第三个比较框的第一个元素)

这里的mid,是左边比较框的最右端再往右一个单位,因此可能会超出数组长度,需要在len和left+offset 中选择较小的一个;

这里的mid,同时也是右边比较框的第一个元素,high是右边比较框的最右端再往右一个单位,与上面同理,有可能越界,所以在left + 2 * offset 和 len 选择较小的一个。这样,再为了方便重新定义几个量来表示,就很清晰明了了:

int left1 = low, right1 = mid;
int left2 = mid, right2 = high;

接下来的部分其实比较简单,就是两个有序数组进行比较,并归并入新数组的过程:

//确定指针没跑到下一个区间去
while (left1 < right1 && left2 < right2)
    b[i++] = a1[left1] < a1[left2] ? a1[left1++] : a1[left2++];

//下头两个while只执行一个,把剩下的元素全搬进去
while (left1 < right1)
    b[i++] = a1[left1++];
while (left2 < right2)
    b[i++] = a1[left2++];

当整个数组比较完以后,我们开辟的b数组中,就是合并过一次的数组了,我们需要把a和b换个位置。

int* temp = a1;
a1 = b;
b = temp;

至此呢,大概已经完成了,但是,不注意free要出现的大问题来了:

如果上面那个交换的代码交换了奇数次,如果我们free(b),我们就会free一片静态内存了,这是不被允许的。如下图所示:

 为了解决这个问题,我们一开始就需要定义指针来指向空间,最后用来做校验。当然,释放的时候我们为了保险直接释放b1就行了,绝对没问题。

 这边我们应该这么写:

if (b != b1)
	{
		for (int i = 0; i < length; i++)
		{
			a[i] = a1[i];
		}
	}
	free(b1);

肯定有人会跟我有一样的问题,为什么不能再改变指针的位置直接成功呢?就像下面一样:

if (b != b1)
	{
		a = a1;
	}
	free(b1);

这个时候我们用地址找一下,就很容易明白了:

一开始的地址:

处理完的地址:

 

 我们会发现其实他所有的地址全都指向了一开始开辟的,最后要被释放掉的动态区域b!

那么为什么编译器不报错呢?这里应该和我在别处了解到的别的一样:编译器会做一定的保留。

所以并不会直接报错,但是会出现意料之外的情况。因此,我们不能改动地址,而应该用赋值操作,来使a数组,与a1数组同步(像我上面讲的,a1一定是有序的那一个,而我们最后需要的是a数组)

核心代码:

这边的核心代码为了阅读以及复制方便,我就只保留 归并排序 的内容了,我的一些试错及写出来的体验和心得就放在下面的测试代码中了,想具体了解的,可以移步到下面了。

void MergeSort(int *a,int length)
{
	int* a1 = a;
	int *b = (int*)malloc((length+5) * sizeof(int));
	int* b1 = b;
	int offset;//offset是每次归并的两个小数组的偏移量
	for (offset = 1; offset < length; offset *=2)
	{
        int left;
		int i=0; //i是给新数组准备的下标
		
		for (left = 0; left < length; left += 2 * offset)
		{
            int low = left, mid = min(left + offset, length), high = min(left + 2 * offset, length);
            int left1 = low, right1 = mid;
            int left2 = mid, right2 = high;
			
            while (left1 < right1 && left2 < right2)
                b[i++] = a1[left1] < a1[left2] ? a1[left1++] : a1[left2++];

            while (left1 < right1)
                b[i++] = a1[left1++];
            while (left2 < right2)
                b[i++] = a1[left2++];

		}
		
		int* temp = a1;
		a1 = b;
		b = temp;
	}
	if (b != b1)
	{
		for (int i = 0; i < length; i++)
		{
			a[i] = a1[i];
		}
	}
	free(b1);
}

整体测试代码:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define N 30

//之前写过了,就不再全写一遍了
void generate_random_number(int*, int, int);
void swap(int*, int*);

//自下而上迭代
void MergeSort(int *a,int length)
{
	int* a1 = a;//如果觉得这一步是多余的,那xd你和我一开始觉得一样,但其实这个是必要的!往下看。
	int *b = (int*)malloc((length+5) * sizeof(int));
	int* b1 = b;
	int offset;//offset是每次归并的两个小数组的偏移量
	for (offset = 1; offset < length; offset *=2)
	{
        int left;
		int i=0; //i是给新数组准备的下标
		//left是每次进行归并的左边小数组,right是右边的;每次归并完两个,整体向右挪动两倍的offset
		
		//注释掉的是写的错的代码。错在没有考虑数组指针越界的情况,其实也就一行 right = min(left+offset,length)
		//for (left = 0,right = left + offset,i = 0; left < length && right < length; left += offset * 2, right = left + offset) 
		//{
		//	int left1 = left;
		//	int right1 = right;
		//	while (right1 < left + 2*offset && left1 < right)//指针没跑到下一个区间去
		//	{
		//		if (a[left1] <= a[right1])
		//			b[i++] = a[left1++];
		//		else
		//			b[i++] = a[right1++];
		//	}
		//	//下头两个while只执行一个,把剩下的元素全搬进去
		//	while (left1 < right)
		//		b[i++] = a[left1++];
		//	while(right1 < left + 2 * offset)
		//		b[i++] = a[right1++];
		//}

		for (left = 0; left < length; left += 2 * offset)
		{	//下这两步都是为了确保数组下标不会越界
            int low = left, mid = min(left + offset, length), high = min(left + 2 * offset, length);
            int left1 = low, right1 = mid;
            int left2 = mid, right2 = high;
			
			//确定指针没跑到下一个区间去
            while (left1 < right1 && left2 < right2)
                b[i++] = a1[left1] < a1[left2] ? a1[left1++] : a1[left2++];

			//下头两个while只执行一个,把剩下的元素全搬进去
            while (left1 < right1)
                b[i++] = a1[left1++];
            while (left2 < right2)
                b[i++] = a1[left2++];

		}
		
		int* temp = a1;
		a1 = b;
		b = temp;
		/* 不容易,我终于想懂了!
		上面这一步实现交换,使得有序的序列肯定是a所指的地址,但是最后a的地址不一定能回到arr,可能在b上
		但是,free函数只能释放动态开辟的内存,也就是只能释放原先b所指的内存空间,如果释放到a,直接严重弹框报错
		外头我画了张图

		根据log2(length)算出来的不同值,下面的交换步骤可能只进行奇数次,因此还需要多一些步骤才可以完全实现
		如果只进行奇数次的话,我函数传参时传入的虽然是真实地址,但是他指向了我在函数中开辟的临时堆空间。
		这块堆空间在函数进行完以后会释放掉,因此如果进行奇数次的话,我们在函数结束以后,原函数的地址是一片未知区域!
		如果确定只进行偶数次,是可以不要下面步骤的
		*/

	}
	/*
	上面的解释也告诉我们,为什么要在函数开头看似无用的新建一个a变量和b1指向我们传入的和创建的地址,也是我们在最后要保证能找到我们一开始的地址
	记录了以后我们可以简便的使用 b != b1 来进行判断。
	*/
	if (b != b1)
	{
		for (int i = 0; i < length; i++)
		{
			a[i] = a1[i];
		}
	}
	free(b1);
}

int main()
{
	int arr[N + 10] = { 0 };
	generate_random_number(arr, 0, 1024);

	MergeSort(arr,N);

	printf("MergeSort排序后数列:\n");
	for (int i = 0; i < N; i++)
		printf("%d ", arr[i]);
	printf("\n");


	return 0;
}

测试结果:

至此,归并排序完成

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MergeSort(迭代归并排序)——C语言实现 的相关文章

  • 贝叶斯网络python实现_朴素贝叶斯和贝叶斯网络算法及其R语言实现

    原标题 xff1a 朴素贝叶斯和贝叶斯网络算法及其R语言实现 作者 xff1a 鲁伟 一个数据科学践行者的学习日记 数据挖掘与机器学习 xff0c R与Python xff0c 理论与实践并行 个人公众号 xff1a 数据科学家养成记 微信
  • 中序计算式的计算器模型C语言实现

    span class token macro property span class token directive keyword include span span class token string lt stdio h gt sp
  • C语言实现16进制数与10进制数的转化

    C语言实现16进制数与10进制数的转化 这里有两种情况 xff1a 第一种情况 xff1a 如果我得到的是一个16进制数 xff0c 我通过肉眼看到的就是16进制显示 xff08 这里看到的肯定打印结果 xff09 xff0c 比如85 x
  • 每个程序员半小时内必须解决的5个编程问题(C语言实现)

    最近关注的几个算法的公众号都看到了如题的一篇文章 xff0c 后1道题单拿出来我肯定不能半个小时内解决 前三道题非常基础 xff0c 相信大部分人能用自己熟悉的语言很快解决 xff0c 而且解决的方法可以多种多样 xff0c 这里说一下我对
  • C 语言实现 FTP 服务器

    这个有专门的课程讲解我看到 xff0c 百度也能搜到不少相关的 我觉得你可以去把这个弄懂
  • C语言实现mavlink库与px4通信仿真

    参照网址 http mavlink io en mavgen c 首先从github上下载对应的C uart interface example 对应的github仓库网址为https github com mavlink c uart i
  • C语言实现http请求器

    C语言实现http请求器 项目介绍 本项目完成一个http客户端请求器 xff0c 该请求器往服务器发送请求 xff0c 并接受服务器发来的响应数据 程序执行流程 建立TCP连接在TCP连接获得的socket的基础上 xff0c 发送htt
  • C语言实现UDP通信

    UDP通信 UDP是一种无连接的尽最大努力交付的不可靠连接 xff0c 通信之前无需先建立连接 xff0c 自然而然 xff0c 通信之后也就无需再释放连接 通信的套接字 UDP所采用的通信接口与前面讲过的TCP通信接口相同 xff0c 只
  • C++/C语言实现HTTP的GET和POST请求

    阅读目录 HTTP请求和IP TCP 实现GET请求 实现POST请求 xff1a 参考 xff1a 回到顶部 HTTP请求和IP TCP 所谓的HTTP协议是基于IP TCP协议的 xff0c 所以要获取远端的html数据只要创建sock
  • C语言实现“井字棋”游戏(三子棋)人机对弈

    井字棋游戏 xff1a 即三子棋 xff0c 英文名叫Tic Tac Tic xff0c 是一种在3 3格子上进行的连珠游戏 xff0c 和五子棋比较类似 xff0c 由于棋盘一般不画边线框 xff0c 格线排成井字故得名 题目分析 xff
  • SHA1校验算法C语言实现

    SHA1 安全哈希算法 xff1a 对于长度小于2 64位的消息 1M 61 1024k 1K 61 1024字节 xff0c 1BYTE 61 8bit 可以想象一下2的63次方位可以表示一个多大的数据文件 xff0c SHA1会产生一个
  • C语言实现Windows下的socket编程

    一 UDP 数据报 协议 UDP User Datagram Protocol的简称 xff0c 中文名是用户数据报协议 xff0c 是OSI Open System Interconnection xff0c 开放式系统互联 参考模型中一
  • 用 PHP 编写合并排序

    我尝试在 PHP 中编写一个涉及小数组的基本合并排序 但问题是它需要大约一分钟左右的时间来执行 并返回 致命错误 允许的内存大小 536870912 字节已耗尽 已尝试 分配 35 个字节 在 Users web www merge php
  • 动态增加java堆空间

    我编写了一个 java 程序 用于测试具有不同数量处理器的不同机器上的几个多线程算法的速度 在某些机器上 合并排序 会失败 因为它需要相当大的堆空间才能处理非常大的数组 我可以在运行程序之前轻松地自己更改 java 堆空间 但我觉得更健壮且
  • 归并排序最有效的实现

    所以我想知道 Java 中合并排序最有效的实现是什么 如果它的时间效率会根据语言而变化 这个问题可能很微不足道 但我的最终目标是向更有经验的程序员学习 这是我做的两个例子 version I made public static doubl
  • 为什么归并排序中的归并操作是O(n)?

    对于归并排序分而治之的操作 自下而上的归并阶段需要多少时间 我的老师说它是线性的 因此它将是O n 但我没有明白 它将如何线性化 合并操作如何是线性的O n 两个数组的合并操作 是扫描数组并选择两个数组中的最低 最高 所以你有了 a 1 3
  • Haskell 中的合并排序

    我是 Haskell 的新手 我正在尝试在其中实现一些已知的算法 我已经对字符串实现了合并排序 我有点失望 我的 Haskell 实现与 C 和 Java 实现相比的性能 在我的机器 Ubuntu Linux 1 8 GHz 上 C gcc
  • 使用合并排序对列表进行合并和排序

    这是我的代码 def merge lists all lst if len all lst lt 2 return all lst 0 get rid of the extra left merge lists all lst len al
  • Java Collections.sort(nodes) 使用什么排序?

    我认为是MergeSort 即O n log n 但是 以下输出不同意 1 0000000099000391 0000000099000427 1 0000000099000427 0000000099000346 5 0000000099
  • C++ 中的迭代合并排序

    我目前正在研究合并排序的迭代版本 但遇到了问题 当数组的特定大小如 34 35 36 或 100 仅几个示例 时 程序会崩溃 而它适用于其余数组 fe 适用于 2 的幂 我已经运行了一些测试并对其进行了调试 问题似乎出在我的迭代 合并排序的

随机推荐

  • Android FFmpeg应用简析

    FFmpeg简介 FFmpeg是一个跨平台的自由软件 xff0c 可用于录制 转换和流式传输音频和视频 它包含了非常多的音频 视频编解码库 封装格式库以及工具库 它不仅支持各种常用的音视频格式 xff0c 而且支持一些非常罕见的格式 FFm
  • Android 机器学习模型的轻量级框架 TensorFlow Lite

    TensorFlow Lite 简介 TensorFlow Lite 是一款用于在移动设备 嵌入式设备和物联网设备上运行机器学习模型的轻量级框架 它是 TensorFlow 在移动领域的延伸 xff0c 旨在解决手机等设备上机器学习计算资源
  • 跨越屏幕:桌面PC端的多端开发框架介绍

    目前 xff0c 随着互联网和移动互联网的发展 xff0c 多端开发框架已经成为越来越多开发者更好的选择 主要有以下几个方面的前景 xff1a 跨平台开发需求不断增加 xff1a 由于不同平台和设备的差异性 xff0c 开发人员需要使用不同
  • Android App 换肤实现方式

    Android App 换肤的引入意味着给用户提供不同的界面样式 xff0c 以适应不同用户的审美需求 引入换肤可以让用户更加个性化地使用 App xff0c 增强用户对 App 的黏度和使用体验 Android App 换肤可以满足以下几
  • Android 开发中常见的架构设计模式组件化、插件化和模块化

    在 Android 中 xff0c 组件化 插件化和模块化都是很常见的架构设计手段 xff0c 用于提高应用开发的灵活性 扩展性和复用性 组件化 插件化和模块化可以混合使用 xff0c 根据项目的需求和规模选择合适的方案 组件化 Compo
  • tensorflow各个版本的CUDA以及Cudnn版本对应关系(重点)

    概述 xff0c 需要注意以下几个问题 xff1a xff08 1 xff09 NVIDIA的显卡驱动程序和CUDA完全是两个不同的概念哦 xff01 CUDA是NVIDIA推出的用于自家GPU的并行计算框架 xff0c 也就是说CUDA只
  • 在PaaS上代理出现了异常的解决方案

    前言 xff1a 我们的项目基本都是在内网的 xff0c 但是当你要访问第三方的插件或者是和第三方做集成时 xff0c 需要后台与第三方接口做连接的 xff0c 这个时候需要通过公司的代理服务器去访问外网 方法一 xff1a 通过Java添
  • 记一次pip下载包报错ERROR: No matching distribution found for xxx时的解决方案

    前言 当我们使用python自带的pip安装一些包时 xff0c 可能会报以下错误 xff1a 出现这种情况有三种可能 xff1a 第一种可能 xff1a pip的版本过低 xff0c 需要升级一下 xff0c 可以执行以下命令进行尝试 p
  • linux系统的7种运行级别

    Linux系统有7个运行级别 runlevel 运行级别0 xff1a 系统停机状态 xff0c 系统默认运行级别不能设为0 xff0c 否则不能正常启动 运行级别1 xff1a 单用户工作状态 xff0c root权限 xff0c 用于系
  • C语言:求 1! + 2! + 3! + ... + n!(for循环)

    解决问题 xff1a C语言利用 for循环 xff1a 求 1 43 2 43 3 43 43 n 代码实现 include lt stdio h gt int main void int n 61 0 int i 61 0 int m
  • Selenium之Css定位元素

    Selenium之Css定位元素 xff1a cssSelector定位 xff0c 属于CSS高级等位 xff0c 它的定位方式 xff0c 利用选择器进行的 在CSS 中 xff0c 选择器是一种模式 xff0c 用于选择需要添加样式的
  • Ubuntu双系统、ROS、软件安装教程

    一 win10下安装Ubuntu16 04双系统 1 制作系统U盘 下载Ubuntu16 04 我们首先去Ubuntu官网下一个Ubuntu16 04的iso镜像文件 利用软碟通制作 在制作系统U盘的时候我们需要去下一个软件 软碟通 xff
  • Matlab并行计算(新手)

    Matlab并行计算 1 Matlab不会自动开启多核并行2 Matlab并行过程 parpool3 电脑核数与parpool的关系4 说明 matlabpool与partool5 并行实现 parfor或SPMD5 1 parfor xf
  • linux打开防火墙端口

    先打开端口 firewall cmd zone 61 public add port 61 8888 tcp permanent 命令8888就是端口 xff0c 直接替换 然后重启防火墙 firewall cmd reload
  • CSS动画(animation)【一】

    1 动画 xff08 animation xff09 属性简介 2 动画实现 64 keyframes wave 0 css样式 10 css样式 100 css样式 3 示例 xff08 vue xff09 lt template gt
  • Js对象数组查找对象属性的值

    let students 61 name 39 小明 39 age 9 name 39 小李 39 age 14 name 39 小白 39 age 12 let index 61 studens findIndex function st
  • sql汇总

    一 xff1a SQLSERVER 1 dateadd 日减一 update tableName set time 61 DATEADD DAY 1 time 小时加10 update tableName set time 61 DATEA
  • 微信开发者工具 显示区域鼠标不显示的问题

    1 xff1a 打开控制面板 2 xff1a 硬件和声音 3 xff1a 鼠标 4 xff1a 勾选显示鼠标轨迹 OK
  • maven导入本地jar

    cmd 然后进入maven库根目录 mvn install install file Dfile 61 D maven lingpipe 4 1 2 jar DgroupId 61 com aliasi DartifactId 61 lin
  • MergeSort(迭代归并排序)——C语言实现

    前言 xff1a 归并排序跟快速排序有异曲同工之妙 xff0c 都是分治法的典型代表 但是这种分治法都有不小的弊端 xff0c 就是需要占用大量的系统栈 xff0c 很容易造成空间的大量浪费 xff0c 所以就有用迭代来优化递归的操作 这次