归并排序&计数排序

本文介绍了归并排序的基本思想、递归和非递归实现,以及计数排序的工作原理和应用场景,重点讨论了它们的时间复杂度和空间复杂度。
摘要由CSDN通过智能技术生成

归并排序

        你们知道高考每个省的一本、二本、专科分数线是如何划分出来的吗?简单地说,就是根据全省的成绩排名来的。所谓全省排名,就是每个市、每个县、每个学校、每个班级的排名合并后在排名得到的。注意我这里用到了合并一词。

        我们两个学生的成绩高低是很容易的,比如甲比乙分数低,丙比丁分数低。那么我们也就很容易得到甲乙丙丁合并后的成绩排名,同样的,戊己庚辛的排名也很容易得到,由于他们两组已经分别有序了,把他们八个学生成绩合并有序,也是很容易可以做到的,继续下去……最终完成全省学生的成绩排名,此时的省高考状元也就诞生了。

基本思想

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

 请结合图示理解:

 再来看一组动图

 下面结合代码再来理解亿遍

代码演示

递归

void _MergeSort(int* arr, int left, int right, int* tmp)
{
    //如果区间内只有一个数据,则说明数据有序,直接返回
	if (left == right)
		return;
	int mid = (left + right) / 2;

	//分治递归
	//[left,mid] [mid + 1,right]
	_MergeSort(arr, left, mid, tmp);
	_MergeSort(arr, mid + 1, right, tmp);

	//归并
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = begin1;
	//比较两个区间的大小,小元素尾插到tmp
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])
			tmp[i++] = arr[begin1++];
		else
			tmp[i++] = arr[begin2++];
	}
    //如果begin1 <= end1,直接把区间里的剩余元素全部拷贝到tmp
	while (begin1 <= end1)
	{
		tmp[i++] = arr[begin1++];
	}
    //如果begin2 <= end2,直接把区间里的剩余元素全部拷贝到tmp
	while (begin2 <= end2)
	{
		tmp[i++] = arr[begin2++];
	}

	//把tmp数组的内容拷贝到arr数组
	for (int j = left; j <= right; j++)
	{
		arr[j] = tmp[j];
	}
}

void MergeSort(int* arr, int sz)
{
	int* tmp = (int*)malloc(sizeof(int) * sz);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	//调用子函数
	_MergeSort(arr, 0, sz - 1, tmp);

	free(tmp);
	tmp = NULL;
}

参考代码已奉上,希望大家能够理解

非递归

        我们常说,“没有最好,只有更好”。递归排序大量引用了递归,尽管代码上比较清晰,容易理解 ,但这会造成空间上的损耗。那我们能不能叫递归转化成迭代呢?结论肯定是可以的。来看代码。

void MergeSortNonR(int* arr, int sz)
{
	int* tmp = (int*)malloc(sizeof(int) * sz);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
    //gap为归并每组的数据个数
	int gap = 1;
	while (gap < sz)
	{
        //这里的j每次加上一对区间的大小
		for (int j = 0; j < sz; j += 2 * gap)
		{
            //区间1
			int begin1 = j, end1 = begin1 + gap - 1;
            //区间2
			int begin2 = end1 + 1, end2 = begin2 + gap - 1;
			int i = j;
            //非递归要特别注意这里,修正每个区间的值,避免越界的情况
            //上面已经说明begin1是小于sz的,所以begin1无需修正
            //如果end1大于等于sz或者begin2大于等于sz,就不需要继续归并,
            //因为那时区间里的数据已经有序,所以直接跳出本次循环就可以
            //但是如果end2大于等于sz,就需要修正end2的值,继续归并
			if (end1 >= sz || begin2 >= sz)
				break;
			if (end2 >= sz)
				end2 = sz - 1;

			//比较大小,小的尾插tmp
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] < arr[begin2])
					tmp[i++] = arr[begin1++];
				else
					tmp[i++] = arr[begin2++];
			}
			while (begin1 <= end1)
			{
				tmp[i++] = arr[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[i++] = arr[begin2++];
			}

			//每归并完一组,就拷贝一次
			for (int y = j; y <= end2; y++)
				arr[y] = tmp[y];
		}
		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

 参考代码已经奉上,上面代码的注释特别详细,希望大家能够理解。

复杂度分析

        一趟归并排序需要将相邻的两个区间进行归并,并且要将tmp数组的元素拷贝到arr数组中,这需要将待排序序列中的所有记录扫描一遍,要耗费O(N)时间,而由完全二叉树的深度可知,整个归并排序需要进行logN次,因此,总的时间复杂度为O(N * logN),而且这是归并排序算法中最好、最坏、平均的时间性能。

        另外,由于递归需要额外开辟栈空间,所以空间复杂度为O(N)。并且归并排序是一种稳定的排序算法。

计数排序

基本思想

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用

 请看实现过程:

实现过程:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中

 代码演示

void CountSort(int* arr, int sz)
{
	int min = arr[0], max = arr[0];
	//选出最大值和最小值
	for (int i = 0; i < sz; i++)
	{
		if (min > arr[i])
			min = arr[i];
		if (max < arr[i])
			max = arr[i];
	}

	//求出数值范围
	int range = max - min + 1;

	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail");
		return;
	}
	//将count数组的内容全部初始化为0
	memset(count, 0, sizeof(int) * range);

	//遍历arr数组,统计数据出现的次数
	for (int i = 0; i < sz; i++)
	{
		//相对映射,减去最小值
		count[arr[i] - min]++;
	}
	//排序
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		//count[i]不为空就继续
		while (count[i])
		{
			//把上面减去的最小值加上,赋值给arr数组
			arr[j++] = i + min;
			count[i]--;
		}
	}
}

参考代码已奉上,希望大家能够理解

总结

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。只能排序整数
  2. 时间复杂度:O(MAX(N,range))
  3. 空间复杂度:O(range)
  4. 稳定性:稳定
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值