【数据结构取经之路】归并排序

news2024/10/6 14:36:09

简介

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

归并排序的思想

归并排序分为分解合并两个步骤。假设数组长度为n.

分解:将数组分割为两个数组,在分别将两个数组分别分割为两个数组,直到最后每个数组都是一个元素,这时将该单元素数组看为有序数组。

合并:将分割出的有序数组进行合并,合并为新的有序数组,如此重复,直到得到一个长度为n的有序数组。

核心操作是将数组中前后相邻的两个有序序列归并为一个有序序列。

归并排序的时间复杂度

O(N * logN)

归并排序的空间复杂度

O(N)

算法稳定性

稳定

 归并排序的递归实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void Print(int* a, int n)
{
	for (int i = 0; i < n; i++) printf("%d ", a[i]);
	printf("\n");
}

void _MergeSort(int* a, int* tmp, int begin, int end)
{
	//分解:当数组只有一个元素时停止
	if (begin >= end) return;    
	int mid = (begin + end) / 2;
	_MergeSort(a, tmp, begin, mid);
	_MergeSort(a, tmp, mid + 1, end);

	//合并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	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 + begin, tmp + begin, sizeof(int) * (end - begin + 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);
}

int main()
{
	int a[] = { 5,2,8,1,9,0,3,6,7,6 };
	int n = sizeof(a) / sizeof(int);
	Print(a, n);
	MergeSort(a, n);
	Print(a, n);

	return 0;
}

先申请空间,再将归并排序的过程作为子函数调用,这样不用在每次递归过程中申请释放空间。上述代码中,_MergeSort为MergeSort的子函数。

归并排序的非递归实现

归并排序的非递归实现是一种比较复杂的算法,它不像快排那样借助栈来存储要处理的区间的范围,而是直接利用循环搞定,此方法要求我们对细节的把控要非常好,尤其是在处理数组边界这一块。

非递归实现归并排序的思想

归并排序,它利用了分治的思想,所谓分治,就是分而治之。不管是用递归来实现还是用非递归来实现,都不可能脱离算法本身的思想。在上述的递归实现过程中我们可以看到,首先是利用递归将数组分解成单个元素的数组(单个元素意味着有序),接着再一一归并,二二归并,四四归并……非递归则是省略了将数组分解为单元素数组的过程,直接引入一个gap代表每组的元素个数,如果令gap = 1,那么每个子数组不就是有序的单元素数组吗?相邻的两个单元素数组归并后,得到了一个有两个元素的有序数组,这时,在令gap = 2,让相邻的有两个元素的数组归并……这一过程和递归方法中的合并过程是一致的。

 两个数组归并,并不要求这两个数组中的元素个数相同。上图中的数字27,在第一趟归并过程中,并没有参与归并,但是随着gap的增大,27也会参与到归并当中,这里,我想说明的是,不用担心归并过程中会漏掉最后一个元素,因为随着gap的增大,它一定会参与到归并中来。

 相邻两个子数组的下标表示

因为在归并过程中,是针对相邻两个数组的,因此,我们得把控好它们的下标。用 i 表示原数组的下标,begin1、end1,begin2、end2分别表示第一个子数组和第二个子数组的首尾元素下标。begin1 = i,end1 = i + gap - 1,begin2 = i + gap, end2 = i + 2*gap - 1。

begin1 = i,end1 = i + gap - 1,begin2 = i + gap, end2 = i + 2*gap - 1(均为左闭右闭区间), i += 2*gap(跳过两个子数组),再去算begin1、end1,begin2、end2

以下是一趟归并排序的过程展开图。 

数组边界处理 

数组的边界控制是非递归方法中最为细节的一部分。当数组元素个数不是2的次方的时候就存在越界的问题。越界分为以下三种情况:

1、end1,begin2、end2全部越界

2、begin2、end2越界

3、end2越界

越界的三种情况如下图所示: 

> 第一种情况, end1,begin2、end2全部越界,这时,第二个数组中没有任何元素,而且第一个数组中的元素是有序的,这种情况下不需要归并,也就是说,本趟归并不对第一个数组做任何处理,后面, 随着gap的增大,该组中的数据会参与到归并当中。

> 第二种情况,begin2、end2越界,同第一种情况,第一个数组中有元素,第二个数组中没有元素,不需要归并。

> 第三种情况,end2越界,第一个数组和第二个数组中都有元素, 因此需要归并,考虑到end2是越界的,所以需要对end2进行修正,将end2修改为 n - 1(n为数组总元素个数),即将end2修改为最后一个元素的下标,然后再归并。

非递归实现归并排序的代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void Print(int* a, int n)
{
	for (int i = 0; i < n; i++) printf("%d ", a[i]);
	printf("\n");
}

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

	int gap = 1;//gap为每个子数组的元素个数,只有一个元素就代表着有序
	while (gap < n)
	{
		int j = 0;
		for (int i = 0; i < n; i += 2 * gap)
		{
			//子数组的下标
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

            //第一、第二种情况,第二个数组中没有元素,不需要归并
			if (end1 >= n || begin2 >= n) break;

            //第三种情况,两个子数组中都有元素,需要修正end2,然后完成归并
			if (end2 >= n) end2 = n - 1;

			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++];

            //归并一组就拷贝一组,不是整体(n)拷贝
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
}

int main()
{
	int a[] = { 5,2,7,1,9,3,6,7,0 };
	int n = sizeof(a) / sizeof(int);
	Print(a, n);
	MergeSortNonR(a, n);
	Print(a, n);
	return 0;
}

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

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

相关文章

在Docker上传我们自己的镜像(以springboot项目为例)

首先确定好在我们的centOS服务器上已经安装并配置好docker 配置自己的springboot镜像并运行 获取springboot的jar包 maven clean--》mavenue package --》复制target目录下生成的jar包 在服务器选择一个文件夹上传jar包&#xff0c;我这里选用的文件夹叫做/opt/dockertest…

如何在HomeAssistant智能家居系统中添加HACS集成并实现无公网IP远程连接家中设备

文章目录 基本条件一、下载HACS源码二、添加HACS集成三、绑定米家设备 ​ 上文介绍了如何实现群晖Docker部署HomeAssistant&#xff0c;通过内网穿透在户外控制家庭中枢。本文将介绍如何安装HACS插件商店&#xff0c;将米家&#xff0c;果家设备接入 Home Assistant。 基本条件…

Python the code is unreachable

Python the code is unreachable 正文 正文 相信有不少小伙伴在使用 Python 的时候有时候会遇到 the code is unreachable 这样的 warning 提示。这种提示表示在我们当前书写的代码种有一部分代码被屏蔽了。可能会存在潜在的 bug&#xff0c;需要我们注意&#xff0c;那么什么…

2023年蓝桥杯省赛——幸运数字

目录 题目链接&#xff1a;0幸运数字 - 蓝桥云课 (lanqiao.cn) 解法 思路 高级思路 总结 题目链接&#xff1a;0幸运数字 - 蓝桥云课 (lanqiao.cn) 解法 首先是我写了差不多一个小时的解法&#xff0c;裂开了&#xff0c;为什么我如此废物 思路 寻找第2023个在二进制、八…

弱电工程是什么?常见的类型有哪些?

一、什么是弱电工程?强电和弱电的区别有哪些? 弱电工程又叫智能建筑&#xff0c;也叫系统集成工程&#xff0c;所有与信息有关的都属于弱电这一块的。弱电是相对于强电而言的强电和弱电从概念上讲&#xff0c;一般是容易区别的&#xff0c;主要区别是用途的不同。强电是用作…

Compute Express Link (CXL): An Open Interconnect for Cloud Infrastructure——论文阅读

DAC 2023 Paper CXL论文阅读笔记整理 背景 Compute Express Link是一种开放的行业标准互连&#xff0c;在PCI Express&#xff08;PCIe&#xff09;之上提供缓存和内存语义&#xff0c;具有资源池和织物功能。本文探讨了CXL在解决云基础设施中的一些挑战方面的作用。 CXL主要…

Python:文件的操作

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; Python的os库主要用于与操作系统进行交互&#xff0c;它提供了多种功能&#xff0c;使得在Python程序中处理操作系统级任务变得容易。这里是一些…

外包干了3个月,技术明显进步。。。。。

在湖南的一个安静角落&#xff0c;我&#xff0c;一个普通的大专生&#xff0c;开始了我的软件测试之旅。四年的外包生涯&#xff0c;让我在舒适区里逐渐失去了锐气&#xff0c;技术停滞不前&#xff0c;仿佛被时间遗忘。然而&#xff0c;生活的转机总是在不经意间降临。 与女…

1.MongoDB的特点与应用场景

什么是 MongoDB &#xff1f; MongoDB 是基于 C 开发的 NOSQL 开源文档数据库 &#xff0c;是最像关系型数据库的 nosql&#xff0c;功能也是最丰富的 nosql&#xff0c;它具有所以的可伸缩性&#xff0c;灵活性&#xff0c;高性能&#xff0c;高扩展性的优势。 大致有如下特…

Vulnhub - Morpheus

希望和各位大佬一起学习&#xff0c;如果文章内容有错请多多指正&#xff0c;谢谢&#xff01; 个人博客链接&#xff1a;CH4SER的个人BLOG – Welcome To Ch4sers Blog Morpheus 靶机下载地址&#xff1a;Matrix-Breakout: 2 Morpheus ~ VulnHub 0x01 信息收集 Nmap扫描…

代码随想录算法训练营第二十四天|● 理论基础 ● 77. 组合(JS写法)

回溯理论基础 回溯法解决的问题都可以抽象为树形结构&#xff0c;因为回溯法解决的都是在集合中递归查找子集&#xff0c;集合的大小就构成了树的宽度&#xff0c;递归的深度&#xff0c;都构成的树的深度。递归就要有终止条件&#xff0c;所以必然是一棵高度有限的树&#xff…

一篇搞定ECharts的基本使用,赶快收藏起来学习吧~

准备工作 引入 声明一个有宽高的dDOM元素 echarts.init(DOM) option配置对象 echarts.setOptions(option) 基础配置 option类似于一个容器&#xff0c;那么里面的属性就相当于组件&#xff1a; xAxis&#xff08;直角坐标系 X 轴&#xff09;、yAxis&#xff08;直角坐…

关于udp能跨局域网传输的问题

UDP&#xff08;用户数据报协议&#xff09;以其独特的传输特性在多种应用场景中都有着极其重要的作用。然而&#xff0c;关于UDP是否能跨局域网&#xff08;LAN&#xff09;进行传输&#xff0c;以及这一传输过程中的优缺点&#xff0c;一直是网络技术领域讨论的热点。本文将详…

git基础命令(四)之分支命令

目录 基础概念git branch-r-a-v-vv-avv重命名分支删除分支git branch -h git checkout创建新的分支追踪远程分支同时切换到该分支创建新的分支并切换到该分支撤销对文件的修改&#xff0c;恢复到最近的提交状态&#xff1a;丢弃本地所有修改git checkout -h git merge合并指定分…

Windows系统安装VNC客户端结合内网穿透实现公网远程连接Deepin桌面

文章目录 1. 安装x11vnc2. 本地远程连接测试3. Deepin安装Cpolar4. 配置公网远程地址5. 公网远程连接Deepin桌面6. 固定连接公网地址7. 固定公网地址连接测试 x11vnc是一种在Linux系统中实现远程桌面控制的工具&#xff0c;它的原理是通过X Window系统的协议来实现远程桌面的展…

1688商品详情API接口采集商品上货

阿里巴巴1688平台并没有直接公开商品详情API接口供普通用户或开发者进行商品采集和上货。1688平台主要服务于批发和采购业务&#xff0c;其API服务通常面向的是有深度合作关系的商家或开发者&#xff0c;且需要经过申请和审核流程。 请求示例&#xff0c;API接口接入Anzexi58 …

Python Web开发记录 Day14:Django part8 订单管理

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 1、数据库准备2、添加订单3、订单列表4、删除订…

C#集合:从字典到队列——探索数据结构核心

文章目录 C# 中的集合类型C# Dictionary 字典C# Hashtable&#xff1a;哈希表Hashtable 类中的属性Hashtable 类中的方法 C# SortedList&#xff1a;排序列表SortedList 类的中的属性SortedList 类的中的方法 C# Stack&#xff1a;堆栈Stack 类中的属性Stack 类中的方法 C# Que…

产品经理:前端实现网页防篡改,你会怎么做?

公众号&#xff1a;程序员白特&#xff0c;欢迎一起交流学习~ 如果产品经理要求系统中某个页面的输入框做防止篡改处理&#xff0c;你会怎么做呢&#xff1f; 需求梳理 首先&#xff0c;什么是防篡改&#xff1f; 简单来说&#xff0c;就是用户输入input框值&#xff0c;我们…

静态HTML5接入海康websocket视频流|海康ws视频流接入H5页面

引言 海康提供了vue实现插件播放视频的实例&#xff0c;实现取流失败了之后重新获取新的流播放视频&#xff0c;但是在很多情况下需要在静态HTML项目中进行视频的播放&#xff0c;于是引出此文。 海康开放平台SDK下载地址&#xff1a;https://open.hikvision.com/download/5c6…