归并排序

归并排序是一种基于分治法的排序算法,通过将两个有序区间归并为一个有序区间来实现排序。本文详细介绍了归并排序的步骤,包括借助辅助数组进行归并,并提供了递归和非递归版本的代码实现。算法的时间复杂度为O(N*logN),空间复杂度为O(N),且该排序算法是稳定的。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.归并排序

归并排序是建立在归并上的一种有效的排序方法,把复杂的问题分解成若干个小文题,是很多复杂问题的 有效解决办法。化繁为简,分而治之。分治法使我们经常会用到的一种方法,归并排序就是采用分治法的一个典型的应用,它是将已经有序的两个区间进行连续归并成一个有效区间,即将两个顺序序列合并为一个顺序序列,我们可以称为二路归并。

这个概念可能比较抽象,我们并不能十分清晰的去理解它的排序过程。再来介绍一下归并排序的详细步骤:

归并排序需要借助辅助数组我们设为:arr3[k];我们设将要归并的两个区间分别为:arr1[i]和arr2[j],第一步:比较arr1[i]和arr2[j]的大小,若arr[i]<=arr[j],则将arr[i]放在arr3中,并让i和k分别+1,如此循环,直到其中一个有序表为空,然后直接将另外一个有序表中剩余的元素直接放在arr3数组里,此时arr3中就是arr1和arr2合并后的有序数组。归并排序的算法我们通常用递归来实现,先把待排序区间中点二分,接着把子左区间排序,再把子右区间排序,最后把左区间和右区间归并为一个有序区间即可完成排序。

来用图直观的感受一下归并排序:


对于归并的过程中借用了辅助数组 arr3;先把arr1和arr2合并的结果放在arr3,再把辅助数组arr3里的有序序列复制到arr数组中,由于比较简单,并未在图中画出。奋斗理解的差不多了,接下来我们来实现一下代码。

2.代码实现,递归和非递归版本

void PrintSort(int arr[],size_t size)
{
	size_t i=0;
	for(; i<size; ++i)
	{
		printf(" %d ",arr[i]);
	}
	printf("\n");
}
//递归版本归并排序
void _MergeArr(int arr[],int beg,int middle,int end,int *tmp)
{
	int cur1=beg;
	int cur2=middle;
	int tmp_index=beg;
	while(cur1<middle&&cur2<end)
	{
		if(arr[cur1]<arr[cur2])//找到将要归并两个相邻子区间的较小值放到辅助数组里
		{
			tmp[tmp_index++]=arr[cur1++];
		}
		else
		{
			tmp[tmp_index++]=arr[cur2++];
		}
	}
		while(cur1<middle)//左区间元素未完,将剩余元素放在辅助数组里
		{
			tmp[tmp_index++]=arr[cur1++];
		}
		while(cur2<end)//右区间元素未完,将剩余元素放在辅助数组
		{
			tmp[tmp_index++]=arr[cur2++];
		}
		//将辅助数组tmp中已经归并好的有序序列复制到原数组arr里
	memcpy(arr+beg,tmp+beg,sizeof(int)*(end-beg));
}
void _MergeSort(int arr[],int beg,int end,int *tmp)
{
	int middle=beg+(end-beg)/2;
	if(arr==NULL||tmp==NULL)
	{
		return;
	}
	if(end-beg<=1)
	{
		return;
	}
	_MergeSort(arr,beg,middle,tmp);//递归分割左子区间使其有序
	_MergeSort(arr,middle,end,tmp);//递归分割右子区间使其有序
	//只有左右子区间均有序再进行归并
	_MergeArr(arr,beg,middle,end,tmp);


}
void MergeSort(int arr[],size_t size)
{
	//开辟size大小辅助存储空间
	int *tmp=(int*)malloc(sizeof(int)*size);
	if(size<=1)
	{
		return;
	}
	_MergeSort(arr,0,size,tmp);//前闭后开区间
	free(tmp);
}
void MergeSortByLoop(int arr[],size_t size)
{
	size_t gap;
	size_t i;
	size_t beg;
	size_t end;
	size_t mid;
	int *tmp=(int*)malloc(sizeof(int)*size);//开辟size大小的辅存
	if(size<=1)
	{
		return;
	}
	gap=1;//初始每组为一个元素
	for(; gap<size; gap*=2)//子区间以2*gap的速度进行扩大
	{
		i=0;
		for(; i<size; i+=gap*2)//每次循环归并好相邻子区间
		{
			beg=i;
			mid=i+gap;
			end=i+2*gap;
			if(mid>size)
			{
				mid=size;
			}
			if(end>size)
			{
				end=size;
			}
			_MergeArr(arr,beg,mid,end,tmp);//归并相邻区间
		}
	}

	free(tmp);
	tmp=NULL;
}
int main()
{
	int arr[]={34,56,77,12,545,23,18,67};
	size_t size=sizeof(arr)/sizeof(arr[0]);
	MergeSort(arr,size);
    PrintSort(arr,size);
	MergeSortByLoop(arr,size);
    PrintSort(arr,size);
	system("pause");
	return 0;
}

运行结果:


3.算法分析

对于归并排序来说,假设有N个数据,时间复杂度为:O(N*logN);刚好最近在网上看到了一种归并排序时间复杂度的计算方法,这里我简单的证明一下:运行时间设为T(N);当把N分为两半时,那么处理N/2子数组花费时间为:T(N/2);合并子区间花费时间与数据个数N成正比,总消费时间=两个大小为N/2的子数组+归并所花费的时间,所以有:T(N)=2*T(N/2)+N;对于N=1,T(1)=1;


T(1)=1;所以有:T(N)=N*logN;

2.空间复杂度

归并排序是一种用空间换时间的做法。额外开辟了大小为N的辅助数组,空间复杂度为:O(N);

3.稳定性

稳定,因为在进行相邻区间归并选取较小元素的时候,是先将前面的元素放在辅助数组里的。

如图:








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值