排序 -- 归并排序与计数排序

排序 – 归并排序与计数排序


一、归并排序(递归)

1.排序思想

在这里插入图片描述
归并排序的基本思想:将已经有序的子序列合并,得到完全有序的序列。即先使每个子序列有序,再将子序列挨个有序合并。
这里先使用递归的思想进行实现,即归并当前两个子序列之前,先使两个子序列有序,将这两个子序列分别堪称新的序列,进行同样的操作,使用递归分治的思想不断递归下去,直到最后一个区间就只有一个数,则返回到上一层递归,然后进行归并。

请添加图片描述

2.代码示例

代码如下:

//归并排序
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)//递归到最后就是begin == end,一个区间就一个数
	{
		return;
	}

	int mid = (begin + end) / 2;
	//[begin, mid] [mid + 1, end]  递归,分治左右区间,让子区间有序
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	//子区间有序后,进行归并[begin, mid] [mid + 1, end]
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin1;
	while (begin1 <= end1 && begin2 <= end2)//当两个子区间都没有走完,进入循环
	{
		//从两个区间中找更小的数放在前面,拷贝到tmp
		if (a[begin1] <= a[begin2])//加等于号,可以控制数据稳定,保持相同大小数据相对顺序不变
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	while (begin1 <= end1)//左区间还没走完,直接赋值到tmp
	{
		tmp[i++] = a[begin1++];
	}

	while (begin2 <= end2)//右区间还没走完,直接赋值到tmp
	{
		tmp[i++] = a[begin2++];
	}

	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));

}

void MergeSort(int* a, int sz)
{
	int* tmp = (int*)malloc(sizeof(int) * sz);//需要一个同样大小的辅助数组来帮助实现
	if (tmp == NULL)
	{
		perror("malloc");
		return;
	}

	_MergeSort(a, 0, sz - 1, tmp);

	free(tmp);
}

3.特性总结

1.归并排序的缺点在于O(N)的空间复杂度,更多是用来解决再磁盘中外排序的问题;
2.时间复杂度:O(N * logN);
3.空间复杂度:O(N);
4.稳定性:稳定。

二、归并排序(非递归)

1.排序思想

归并排序非递归的基本思想:与递归法思想一致,只是使用非递归的方式实现。
使用非递归实现的方法是:将整个序列分为多个子区间,子区间的长度gap从1开始,相邻两个子区间为一组数据,能够进行一次归并操作,直到当前gap下的所有数据都归并完成,将数据拷贝回原数组,区间长度gap变为原来的2倍,循环执行相同操作,直到gap大于等于数组长度sz,循环结束,得到有序数组。
在这里插入图片描述

2.代码示例

代码如下:

void MergeSortNonR(int* a, int sz)
{
	int* tmp = (int*)malloc(sizeof(int) * sz);//需要一个同样大小的辅助数组来帮助实现
	if (tmp == NULL)
	{
		perror("malloc");
		return;
	}

	int gap = 1;//gap是子区间的长度
	while (gap <sz)//当gap小于sz时,进行循环
	{
		for (int i = 0; i < sz; i += 2 * gap)//一组数据两个子区间,循环完后当前gap下的数据就排完一趟了
		{
			//[i, i + gap - 1]  [i + gap, i + 2*gap - 1]这两个区间进行归并
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			//由于区间是计算出来的,当整个数据的个数不是2的n次方时,就会发生越界,所以要对边界进行修正
			//只有end1,begin2,end2会发生越界
			if (end1 >= sz)
			{
				end1 = sz - 1;
				//将[begin2, end2]修正为不存在区间
				begin2 = sz;
				end2 = sz - 1;
			}

			if (begin2 >= sz)
			{
				begin2 = sz;
				end2 = sz - 1;
			}

			if (end2 >= sz)
			{
				end2 = sz - 1;
			}

			//修正完后,开始归并
			int j = begin1;
			while (begin1 <= end1 && begin2 <= end2)//当两个子区间都没有走完,进入循环
			{
				//从两个区间中找更小的数放在前面,拷贝到tmp
				if (a[begin1] <= a[begin2])//加等于号,可以控制数据稳定,保持相同大小数据相对顺序不变
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)//左区间还没走完,直接赋值到tmp
			{
				tmp[j++] = a[begin1++];
			}

			while (begin2 <= end2)//右区间还没走完,直接赋值到tmp
			{
				tmp[j++] = a[begin2++];
			}
		}
		//当前gap下的数据归并完后,拷回原数组
		memcpy(a, tmp, sizeof(int) * sz);
		gap *= 2;//区间长度变为2倍
	}
	free(tmp);
}

一、计数排序

1.排序思想

计数排序的基本思想:统计序列中每个数据出现的次数,根据统计的结果将数据按顺序放回到原来的数组中。
如果数据的范围很大,那么这种排序就会很浪费空间,适合数据范围较小,重复数据多的排序场景。
为了减小空间复杂度,可以使用相对映射来进行计数,即记录的是当前数据与最小值之间的差距,在回写时,把相对映射转为原数据回写就行了。

2.代码示例

代码如下:

//计数排序
void CountSort(int* a, int sz)
{
	//计算最大和最小值
	int max = a[0];
	int min = a[0];
	for (int i = 1; i < sz; i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}

		if (a[i] < min)
		{
			min = a[i];
		}
	}
	//开辟统计次数的数组
	int range = max - min + 1;//数据范围
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc");
		return;
	}
	memset(count, 0, sizeof(int) * range);
	//统计数据出现的次数
	for (int i = 0; i < sz; i++)
	{
		count[a[i] - min]++;//相对映射,代表的是当前数据离最小值的相对距离
	}
	//回写,排序
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		//出现几次就回写几个i + min
		while (count[i]--)
		{
			a[j++] = i + min;
		}
	}
	free(count);
}

3.特性总结

1.计数排序再数据范围较小,重复度较高的场景下,效率很高;
2.时间复杂度:O(MAX(sz, range));
3.空间复杂度:O(range);
4.稳定性:稳定;


总结

本文主要介绍了归并排序的递归写法和非递归写法,以及计数排序的实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值