深入理解并实现——归并排序【C语言】

news2024/9/21 21:45:12

目录

一、概念

二、递归版实现 

三、非递归实现

三、文件归并排序

小结


一、概念

        归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

        其思想可用下图来表示:

        从上图我们可以看到,归并的大体思路为:先保证小区间有序,再保证大区间有序。在思想上体现出了:分而治之的理念。

        可总结为以下两点:

  1. 将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。
  2. 将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。

二、递归版实现 

        对于用递归实现这个排序,我们可这样解决:

        1. 开辟一个新数组,用于存放每次排完序的值。

        2. 找到这个数组的最小单位,两两比较。

        3. 每完成一组排序,便把新数组拷贝给原数组。

        4. 重复以上操作,直到排序完成。

        代码实现: 

void _MergeSort(int* a, int* tmp, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int mid = (left + right) / 2;
	// 如果[begin, mid][mid+1, end]有序就可以进行归并了
	_MergeSort(a, tmp, left, mid);
	_MergeSort(a, tmp, mid + 1, right);
	//归并
	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++];
	}

	memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	_MergeSort(a, tmp, 0, n - 1);

	free(tmp);
	tmp = NULL;
}

三、非递归实现

        我们用递归解决这个排序似乎是件较容易的事情,但对于我们想要用非递归实现来说,仍有不小的挑战。我们说一下实现思路:

        1.我们要解决如何实现分组问题

        2.我们引入gap变量用它来进行控制分组

        3.分组运用gap不同的值来确定每个组的大小,从小往大依次来实现归并。

        注意点:

        1. 当第二组开始位置 超过 / 等于 该数组长度时,我们此时可认为以排序完成,break即可。

        2. 当第二组结束位置  超过 / 等于 该数组长度时,我们要将其大小置为n-1。

        代码实现如下:

void MergeSortNonR(int* a, int n)
{
	int* tmp = malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			// [begin1, end1][begin2, end2]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			// 第二组都越界不存在,这一组就不需要归并
			if (begin2 >= n)
			{
				break;
			}
			// 第二的组begin2没越界,end2越界了,需要修正一下,继续归并
			if (end2 >= n)
			{
				end2 = n - 1;
			}

			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			
		}
		gap *= 2;
	}
	memcpy(a + n - 1, tmp + n - 1, sizeof(int) * (n - 1));
	free(tmp);
	tmp = NULL;
}

        这里大家估计会有点小疑惑,疑惑什么呢?为什么不能tmp归并完我们在把它拷给a,一步一步拷不麻烦吗?

        我们能不能直接拷呢?大家可以去操作一下,答案很显然:不可以! 原因如下:

        这个本身的话,就是每次循环结束,在拷贝数组和临时数组的值进行交换,之后就是在临时数组改变之后的情况下,在进行第二次循环排序,之后。把拷贝后的数据在进行分组合并,每次循环里面都是对a合并后的数据在做处理,如果说全部执行完再拷贝,那a每次并没有啥变化,当然就不可能完成归并排序整个过程。

        各位感兴趣的话可以打印验证一下。 

三、文件归并排序

        关于这个问题,我们给出以下情景:在今年,你怀着忐忑的心情去参加秋招,顺利通过了笔试,在面试时,面试官的问题你都对答入流,直到最后一题:给你1G的空间,你如何使10G的数据有序,这时,你看过本博主写得TOP-K问题(二叉树——堆详解_堆 二叉树-CSDN博客),你自信满满的回答了这个问腿,面试官觉得你很不错,便提问到:如果用归并该如何解决呢?你不由想起了这篇博客,也就是目前各位读者所看的这篇,以下是解题思路:

        1. 首先,先创建三个文件:file1,file2,mfine。

        2.读取n个值排序后写到file1,再读取n个值排序后写到file2

        3. file1和file2利⽤归并排序的思想,依次读取⽐较,取⼩的尾插到mfile,mfile归并为⼀个有序⽂件

        4. 将file1和file2删掉,mfile重命名为file1

        5. 再次读取n个数据排序后写到file2

        6. 继续⾛file1和file2归并,重复步骤2,直到⽂件中⽆法读出数据。最后归并出的有序数据放到了 file1中

        对于删掉文件和改文件名,我们可通过remove 和rename 函数来完成(可点击查看其用法)。

        代码实现:

//造数据
void CreateNDate()
{
	const char* file = "text.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fail error");
		return;
	}
	srand((unsigned)time(NULL));
	int n = 100;
	for (int i = 0; i < n; i++)
	{
		int x = rand() + i;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
int comper(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}

// 返回实际读到的数据个数,没有数据了,返回0
int ReadNDataSortToFile(FILE* fout, int n, const char* file)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		return 0;
	}
	int x = 0;
	// 想读取n个数据,如果遇到文件结束,应该读到j个
	int j = 0;
	for (int i = 0; i < n; i++)
	{
		if (fscanf(fout, "%d", &x) == EOF)
		{
			break;
		}
		tmp[j++] = x;
	}
	if (j == 0)
	{
		free(tmp);
		return 0;
	}
	//快排
	qsort(tmp, j, sizeof(int), comper);

	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("file error");
		return 0;
	}
	// 写回file1文件
	for (int i = 0; i < j; i++)
	{
		fprintf(fin, "%d\n", tmp[i]);
	}

	free(tmp);
	fclose(fin);
	return j;
}

void MergeFile(const char* file1, const char* file2, const char* mfile)
{
	FILE* fin1 = fopen(file1, "r");
	if (fin1 == NULL)
	{
		perror("file error");
		return;
	}
	FILE* fin2 = fopen(file2, "r");
	if (fin2 == NULL)
	{
		perror("file error");
		return;
	}
	FILE* mfin = fopen(mfile, "w");
	if (mfin == NULL)
	{
		perror("file fail");
		return;
	}
	//归并逻辑
	int x1 = 0, x2 = 0;
	int ret1 = fscanf(fin1, "%d", &x1);
	int ret2 = fscanf(fin2, "%d", &x2);
	while (ret1 != EOF && ret2 != EOF)
	{
		if (x1 < x2)
		{
			fprintf(mfin, "%d\n", x1);
			ret1 = fscanf(fin1, "%d", &x1);
		}
		else
		{
			fprintf(mfin, "%d\n", x2);
			ret2 = fscanf(fin2, "%d", &x2);
		}
	}

	while (ret1 != EOF)
	{
		fprintf(mfin, "%d\n", x1);
		ret1 = fscanf(fin1, "%d", &x1);
	}
	while (ret2 != EOF)
	{
		fprintf(mfin, "%d\n", x2);
		ret2 = fscanf(fin2, "%d", &x2);
	}

	fclose(fin1);
	fclose(fin2);
	fclose(mfin);
}
void test()
{
	/*CreateNDate();*/
	const char* file1 = "file1.txt";
	const char* file2 = "file2.txt";
	const char* mfile = "mfile.txt";
	FILE* fout = fopen("text.txt", "r");
	if (fout == NULL)
	{
		perror("file error");
		return;
	}
	int m = 10;
	ReadNDataSortToFile(fout, m, file1);
	ReadNDataSortToFile(fout, m, file2);
	while (1)
	{
		MergeFile(file1, file2, mfile);
		remove(file1);
		remove(file2);
		rename(mfile, file1);
		if (ReadNDataSortToFile(fout, m, file2) == 0)
		{
			break;
		}
	}
}

小结

        本文对于归并排序做了较为深入的讲述。主要讲述了:归并排序的递归版、非递归版以及文件归并排序问题。大家重点掌握归并排序即可,对于学有余力者,可研究其文件归并排序。好了,本文的内容到这里就结束了,如果觉得有帮助,还请一键三连多多支持一下吧!

完!

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

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

相关文章

【论文阅读】Single-Stage Visual Query Localization in Egocentric Videos

paper&#xff1a; code&#xff1a; 简介&#xff1a; 长篇自我中心视频的视觉查询定位需要时空搜索和指定对象的定位。之前的工作开发了复杂的多级管道&#xff0c;利用完善的对象检测和跟踪方法来执行 VQL&#xff08;视觉查询定位&#xff09;。然而&#xff0c;每个阶段…

智能创作与优化新时代:【ChatGPT-4o】在【数学建模】、【AI绘画】、【海报设计】与【论文优化】中的创新应用

目录 1. 引言 什么是ChatGPT4o&#xff1f; 背景与发展历史 2.chatgpt4o数学建模 常见的数学建模专业术语及其简要说明 一个具体的代码例子 问题描述 代码实现 代码说明 运行结果 3.chatgpt4o在论文 1.例如生成基于标签的推荐系统模型及算法研究 1. 摘要 2. 引…

微信搜一搜下面搜索发现是什么?收录规则因素有哪些?如何能被搜索发现话题标签收录?

前言&#xff1a;为什么想到写这个&#xff1f;上周白杨SEO玩赚流量群里的一个群友私下问我怎么能被微信里搜索发现这个话题标签收录&#xff0c;问规则是什么&#xff0c;所以今天就来简单分享一下&#xff0c;如果你也感兴趣&#xff0c;可以看看。 文章大纲&#xff1a; 1、…

Go 1.19.4 文件读写操作-Day 14

1. 文件读写操作 在我们对一个文件进行读写操作前&#xff0c;有一个必做步骤&#xff0c;那就是要先打开文件。 打开文件主要使用os模块的 Open 和 OpenFile 。 Open&#xff1a;适合读。OpenFile&#xff1a;适合读写。 2. 打开文件 2.1 Open 作用&#xff1a; 以只读方式打…

书生浦语-MindSearch

1.目的 利用SiliconCloud提供的免费Intern2.5-7B-Chat的API部署MindSearch。 2.过程 2.1 在GitHub上打开codespace主页 我们首先在GitHub上打开codespace&#xff0c;选择blank template。 然后打开一个Web端的vscode&#xff0c;将MindSearch进行clone mkdir -p /workspa…

二叉树详解(进阶)

目录 1. 二叉搜索树 1.1 基本概念 1.2 基本操作 1.3 性能分析 1.4 键值对 2. AVL树和红黑树 2.1 AVL树 2.2 红黑树 3. 红黑树模拟实现STL中的map与set 1. 二叉搜索树 1.1 基本概念 二叉搜索树&#xff08;BST&#xff0c;Binary Search Tree&#xff09;&#xff1a…

记录一次安装Studio卸载后再次安装反复打不开的问题

先说问题表现&#xff0c;低版本的安装后点击没反应&#xff0c;高版本的报错&#xff0c;如下图&#xff0c;反复卸载安装都没有用&#xff0c;网上也找了各种彻底卸载安卓Studio的方法也不行 Error occurred during initialization of VMagent library failed Agent OnLoad:…

【C++】unordered_set 容器的最全解析(什么是unordered_set?unordered_set的常用接口有那些?)

目录 一、前言 二、预备知识 &#x1f4a2;关联式容器&#x1f4a2; &#x1f4a2;键值对&#x1f4a2; &#x1f4a2;哈希结构的关联式容器&#x1f4a2; 三、unordered_set 详解 &#x1f525;unordered_set 的介绍 &#x1f525;unordered_set 的构造 &am…

解除 Excel 表格的文档保护全攻略

在日常工作和学习中&#xff0c;我们可能会遇到 Excel 表格被保护无法编辑的情况。别担心&#xff0c;今天就为大家分享几种解除 Excel 表格文档保护的方法。 一、导入腾讯文档 可以将受保护的 Excel 表格上传到腾讯文档。在部分情况下&#xff0c;腾讯文档会尝试自动解除表…

零基础国产GD32单片机编程入门(九)低功耗模式实战含源码

文章目录 一.概要二.GD32单片机低功耗基本介绍三.GD32单片机待机模式介绍四.待机低功耗例程实验五.工程源代码下载六.小结 一.概要 在生活中通过关掉用电器可以实现省电节能的目的&#xff0c;同样的道理单片机也可以通过这种方法实现降低功耗。单片机是由许多部件组成&#x…

ruoyi-vue-plus服务端打包报错的问题

对idea不熟&#xff0c;不知道在哪里输入打包命令&#xff0c;只会用手点击进行打包&#xff0c;然后就报错了 官方文档给的打包命令是 mvn clean package -D maven.test.skiptrue -P prod 从命令中可以看到跳过了maven测试&#xff0c;那么就要设置idea打包时跳过测试&…

k8s单master多node环境搭建-k8s版本低于1.24,容器运行时为docker

k8s 1.20.6单master多node环境搭建 1.环境规划2.初始化服务器1&#xff09;配置主机名2&#xff09;设置IP为静态IP3&#xff09;关闭selinux4&#xff09;配置主机hosts文件5&#xff09;配置三台主机之间免密登录6&#xff09;关闭交换分区swap&#xff0c;提升性能7&#xf…

【Python基础】字符串类型

本文收录于 《Python编程入门》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程基础知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、Python 字符串类型2.1 Python访问字符串中的值2.2 Python 转义字符2.3 Python 字符串运算符2.4 Py…

Bluetooth: gatt profile

Gatt 主要是描述了attribute的排列方式&#xff1b; Attribute caching 这个机制允许client只搜索一次server即可&#xff0c;当重连后不需要再搜索直接使用之前的。如果server的服务发生了变化&#xff0c;需要通过 service change indication 告诉client&#xff1b; client…

网优学习干货:2.6G仿真操作(2)

导入仿真区域图层 建立仿真站点组 设置仿真任务-结果图层和楼宇仿真高度 仿真结果统计-结果图层渲染 仿真结果统计-结果导出 目录 导入天线文件-导入方法与覆盖仿真相同&#xff0c;但天线文件需要包含PDSCH波束文件 将Beamforming天线添加到基站 如果在步骤④中没有找到Beamfo…

Web自动化测试实战--博客系统

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;测试&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 1.项目效果展示 2.编写web测试用例 3.自动化测试脚本开发 3.1创建空项目 引…

构建大师:深入理解Linux下的Make和Makefile

引言 在软件开发的世界里&#xff0c;构建过程是一项繁琐而重要的任务。无论是简单的脚本还是复杂的软件项目&#xff0c;都需要一种方式来自动化编译、链接以及测试等过程。在Linux环境下&#xff0c;Make工具和它的配置文件——Makefile&#xff0c;成为了许多开发者构建项目…

计算机硬件的组成

目录 前言 计算机系统组成 计算机硬件的组成 1、控制器 2、运算器 3、主存储器 4、辅助存储器 5、输入设备 6、输出设备 最后 前言 计算机已成为不可或缺的工具。无论是个人电脑还是服务器集群&#xff0c;其背后都是由一系列硬件组件协同工作的结果。 本文讲介绍计…

ssrf攻击本地fastcgi漏洞复现

目录 环境&#xff1a;UbuntuNginxphp 代码 开始测试 查看 环境搭建 环境&#xff1a;UbuntuNginxphp 代码 <?php highlight_file(__FILE__); $url $_GET[url]; $curl curl_init($url);curl_setopt($ch,CURLOPT_URL,$url); curl_setopt($curl, CURLOPT_HEADER, 0…

滚雪球学MyBatis-Plus(02):环境准备

环境准备 本地开发环境参考如下&#xff1a; 开发工具&#xff1a;IntelliJ IDEA 2021.3.2JDK版本&#xff1a; JDK 1.8Spring Boot版本&#xff1a;2.3.1.RELEASEMaven版本&#xff1a;Apache Maven 3.8.2MySQL&#xff1a;5.6 前言 在上期内容中&#xff0c;我们系统地介绍了…