当前位置: 首页 > article >正文

【数据结构】归并排序 —— 递归及非递归解决归并排序

归并排序

  • 一、归并排序
    • 1、归并排序的思想
    • 2、归并排序代码实现(递归)
      • <1> 归并排序的递归区间
      • <2> 归并排序的稳定性
      • <3> 拷贝
    • 3、归并排序代码实现(非递归)
      • <1> 循环区间溢出问题
  • 二、总结

一、归并排序

1、归并排序的思想

假设我们要排一个升序的数组
先看下面的动图
在这里插入图片描述

假设有两组升序数据
我们设置 ptr1 和 ptr2 来代表数组下标
将两数据进行比较,将小的填入一个新的数组
当两个数组中的数都进入新的数组
接下来我们只需要将该数组复制到原数组中
就完成了排序

如果我们要排一个升序数组
就要将它分成两个小的升序数组

若想得到两个小的升序数组
就要将每一个小的升序数组分成两个更小的升序数组

。。。。。。

————————————————————————————
于是我们想到了用递归的方式
一层层递归
直到只剩下一个不用数据,然后返回进行排序

2、归并排序代码实现(递归)

void _MergeSort(int* a, int* tmp, int left, int right)
{
	//返回停止条件
	if (left == right)
	{
		return;
	}

	//取中间值
	int mid = (left + right) / 2;
	
	//递归
	//[left, mid][mid + 1, right]
	//递归左边
	_MergeSort(a, tmp, left, mid);
	//递归右边
	_MergeSort(a, tmp, mid + 1, right);

	//进行单次归并
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;

	//记录起始位置
	int begin = left, end = right;

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


//进行拷贝
    memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));

}

//归并排序
void MergeSort(int* a, int left, int right)
{
	//开辟一个复制数组
	int* tmp = (int*)malloc(sizeof(int) * (right - left + 1));
	if (tmp == NULL)
	{
		perror("malloc fail:");
		exit(1);
	}

	//进行归并排序
	_MergeSort(a, tmp, left, right);

	//数组销毁
	free(tmp);
	tmp = NULL;
}

<1> 归并排序的递归区间

递归区间1
[left, mid][mid + 1, right]
递归区间2
[left, mid - 1][mid, right]

看这两个区间有所不同
上面代码我采用的是区间1
而没有采用区间2
因为区间2会出现死循环,导致栈溢出
看下面的图:

区间1:
在这里插入图片描述
区间2:
在这里插入图片描述
同样的 mid 但是当递归至只剩下两个时,区间2就会陷入死循环

<2> 归并排序的稳定性

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

在比较 begin1 和 begin2 时我们用的是 " <= "
这样前面的数就会在前面
在这里插入图片描述
在图上我们发现我们在面对相同的数时,我们会先将 ptr1 中的数填入数组,那么相同数的相对位置没有发生改变,我们说归并排序是稳定的

<3> 拷贝

//进行拷贝
memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));

我们是边排序边拷贝的
如果等到最后拷贝会有出问题的风险
memcpy函数

3、归并排序代码实现(非递归)

如果是用非递归的方式
我们可以用循环
设置gap变量,gap 每次循环结束 gap *= 2
完成整个数组的归并
在这里插入图片描述

代码实现:

//归并排序(非递归)
void MergeSortNonR(int* a, int left, int right)
{
	//开辟复制数组
	int* tmp = (int*)malloc(sizeof(int) * (right - left + 1));
	if (tmp == NULL)
	{
		perror("malloc fail:");
		exit(1);
	}

	int gap = 1;
	//进行单次排序
	while (gap < right + 1)
	{
		for (int i = 0; i < right + 1; i += gap * 2)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + gap * 2 - 1;
			int begin = i;
			printf("[%d, %d][%d, %d]", begin1, end1, begin2, end2);
			
			//当begin2超过数组区间
			if (begin2 > right)
			{
				break;
			}
			//当只有end2超过区间
			if (end2 > right)
			{
				end2 = right;
			}

			//单次归并
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[begin++] = a[begin1++];
				}
				else
				{
					tmp[begin++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[begin++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[begin++] = a[begin2++];
			}
			//将数组进行复制
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		printf("\n\n");
		gap *= 2;
	}

	//销毁复制数组
	free(tmp);
	tmp = NULL;
}

<1> 循环区间溢出问题

当begin2超过数组区间
if (begin2 > right)
{
	break;
}
当只有end2超过区间
if (end2 > right)
{
	end2 = right;
}

在循环过程中,除了 begin1 不会超出数组区间
其他三个都有可能超出区间
我们将所有区间都打印出来
在这里插入图片描述
我们发现超出区间总共分为三种情况
在这里插入图片描述
当 begin2 和 end1 超出区间时:
说明后面的整个区间都超出了数组的范围
那就不用归并

当 end2 超出区间时:
说明后面的一部分数没有超出区间
可以将 end2 改为区间的最大值,继续进行归并

二、总结

归并排序的时间复杂度为 O(N * logN)
是比较快的排序
但是归并排序有它的缺陷
它的空间复杂度为 O(N)
排序需要开辟空间
当排序数量过大时有风险


http://www.kler.cn/a/406847.html

相关文章:

  • 青少年编程等级考试C++一级,硬币反转问题
  • 【MySQL】MySQL数据库基础
  • 向量数据库FAISS之一:官方简单教程
  • PDF内容提取,MinerU使用
  • Python操作neo4j库py2neo使用之py2neo 删除及事务相关操作(三)
  • 使用最小花费爬楼梯(DP)
  • 基于自混合干涉测量系统的线展宽因子估计算法matlab仿真
  • Python Matplotlib 安装指南:使用 Miniconda 实现跨 Linux、macOS 和 Windows 平台安装
  • MAC C语言 Helloword
  • spring学习(四)
  • DevOps 之 CI/CD入门操作 (二)
  • k8s上面的Redis集群链接不上master的解决办法
  • Powershell 命令行窗口 设置行宽、折行、行省略
  • IText创建加盖公章的pdf文件并生成压缩文件
  • 高级java每日一道面试题-2024年11月22日-JVM篇-说说堆和栈的区别?
  • 纯HTMLCSS实现3D旋转地球
  • 嵌入式C语言面试题 - 2024/11/18
  • 【HM-React】01. React基础-上
  • element-plus教程:Layout 布局
  • 从容器到Podman:一个全方位的剖析
  • 电子应用设计方案-20:智能电冰箱系统方案设计
  • 人工智能与自动驾驶:从梦想到现实
  • 事务、视图、索引
  • Kafka-Consumer理论知识
  • “iOS profile文件与私钥证书文件不匹配”总结打ipa包出现的问题
  • R package安装的几种方式