7、归并排序

文章详细介绍了归并排序的原理,使用分治法将数组不断分裂并排序,然后通过合并有序子序列来构建完全有序的数组。分别展示了递归和非递归两种方法的实现,讨论了其时间复杂度为O(nlogn),空间复杂度为O(n),并指出算法的稳定性。
摘要由CSDN通过智能技术生成

归并排序采取的是分治法去实现排序。

思路:将原数组分裂开(一次分裂一半)后,分别对每一部分进行排序然后拷贝回原数组,而每一部分的排序也是采用分治法,归并排序(直到分裂的只剩下一个元素)

将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

 纵向展开图:

 

 

 该方法是先开辟新的空间,传入数据进行排序,之后再拷贝回原数组,所以需要先开辟新空间,并且出该函数后不销毁

递归方法

因每次原数组分裂后的每一部分都需要再分裂再排序,因此可以采用递归实现,具体解释看代码注释↓ 

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin ==end)                                //当分裂的只剩一个元素就停止
	{
		return;
	}
	int mid = (end + begin) / 2;                    //每次分裂都是一半分
	_MergeSort(a, begin, mid, tmp);                 //分别对每部分进行排序(递归)
	_MergeSort(a, mid+1, end, tmp);

	int begin1 = begin, end1 = mid;                 //对每部分的排序具体方法
	int begin2 = mid + 1; int end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)       //由于每一部分都是有序的,所以分裂的两部分
	{                                              //依次进行比较,谁小谁先进
		if (a[begin1] < a[begin2])
		{
			tmp[i] = a[begin1];
			begin1++;
		}
		else
		{
			tmp[i] = a[begin2];
			begin2++;
		}
		i++;
	}                                               //直到其中一部分没有数据后
	while (begin1 <= end1)                          //把剩余的数据加入tmp中
	{
		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);
	_MergeSort(a,0,n-1,tmp);
	free(tmp);
}

非递归方法

 非递归方法可以说是递归的逆思路

第一趟:分组每个组有个元素,每组进行排序并拷贝

第二趟:分组每个组有个元素,每组进行排序并拷贝

第三趟:分组每个组有个元素,每组进行排序并拷贝

第四趟:分组每个组有个元素,每组进行排序并拷贝

...

直到每组有N个元素(N为原数组元素个数)

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		return;
	}
	int gap = 1;
	while (gap < n)
	{
		for (int j = 0;j<n;j = j+gap*2)
		{
			int begin1 = j, end1 = j+gap-1;
			int begin2 = end1+1 ; int end2 = begin2+gap-1;
			int i = j;
			while (begin1 <= end1 && begin2 <= end2)
			{
				
				if (a[begin1] < a[begin2])
				{
					tmp[i] = a[begin1];
					begin1++;
				}
				else
				{
					tmp[i] = a[begin2];
					begin2++;
				}
				i++;
			}
			while (begin1 <= end1)
			{
				tmp[i++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[i++] = a[begin2++];
			}
		}
        memcpy(a , tmp , sizeof(int) *n);
		gap *= 2;
	}
	free(tmp);
}

当然,这个代码有问题,只能运行满足2^n个元素的数组排序,因为没有考虑越界问题

 无妨换个思路,每次比较后就拷贝回去,而不是一趟拷贝回一次,这样的话,当前两种情况就可以直接跳出比较的循环,因为它只有一半,没有另一半进行比较,而且本身已经有序,就在原数组不变即可,不需要拷贝进tmp(因为拷贝到tmp最终会拷贝回原数组中,那一部分并没有变化),而第三种情况只需将end2修改到边界n-1处即可

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		return;
	}
	int gap = 1;
	while (gap < n)
	{
		for (int j = 0;j<n;j = j+gap*2)
		{
			int begin1 = j, end1 = j+gap-1;
			int begin2 = end1+1 ; int end2 = begin2+gap-1;
			int i = j;
			if (end1 >= n||begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			while (begin1 <= end1 && begin2 <= end2)
			{
				
				if (a[begin1] < a[begin2])
				{
					tmp[i] = a[begin1];
					begin1++;
				}
				else
				{
					tmp[i] = a[begin2];
					begin2++;
				}
				i++;
			}
			while (begin1 <= end1)
			{
				tmp[i++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[i++] = a[begin2++];
			}
			memcpy(a + j, tmp + j, sizeof(int) * (end2 - j + 1));
//每次拷贝回并不是整个tmp,而是每个部分,所以用a+j,tmp+j,end2-j+1也是控制了每部分的元素个数
//当end2越界时,个数会发生改变,所以不是2*gap
		}
		gap *= 2;
	}
	free(tmp);
}

时间复杂度:O(nlogn)

空间复杂度:O(n) 

稳定性:稳定

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值