归并排序和计数排序

排序主要有八种,前面6种在之前已经介绍并实现过了。

这是前面的6大牌排序介绍:http://t.csdn.cn/l06fT

归并排序:

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

下面让我们模拟实现归并排序:

实现的方式是用一个临时数组去存储归并后的数据,然后再拷贝回去,如果直接在原数组去实现的话,就需要进行数据的移动,这样很可能就会导致数据的丢失。归并排序递归实现和二叉树的后序遍历有点像,都是访问都最后,然后出来的左右两组数据之后再递归的根的位置。

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
	{
		return;
	}
	int mid = (begin+end)/2;
	//直接递归到最底,归并排序相当于二叉树的后序遍历
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid+1, end , tmp);
	int begin1 = begin; int 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++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
 	}
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	//复制回原数组
	memcpy(a + begin, tmp + begin, (end2 - begin+1) * sizeof(int));
}

void MergeSort(int* a, int n)
{
	//创建临时数组
	int* tmp = (int*)malloc(n * sizeof(int));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
	tmp = NULL;
}

 归并排序并不像快排一样,快排的递归深度为O(N),所以递归的方法对于快排来说用处不大,所以快排通常使用非递归的方式去解决,也就是用队列来模拟递归的过程,对于归并排序来说它的递归深度并不高,是log(N),所以使用递归去实现归并排序也是没有问题。但这里也会实现一下归并排序的非递归方式

归并排序的非递归方式:
对于归并排序的非递归方式,我们不能使用简单的栈或者队列来模拟实现递归的过程,因为递归的过程像后序遍历,但是我们可以使用循环的方式去模拟实现,就像之前所学过的斐波那契数列一样,也可以使用循环的方式直接实现。

思路:我们第一次可以一个一个分开,然后两个两个进行归并,然后再4个4个进行归并。当然这其中有些特殊情况,我们需要处理一下。

特殊情况及其出来方式:
1.第一组数据的部分缺失:

 2.第二组数据全部缺失:

3.第二组数据部分缺失:

 以上3种情况都可能出现,可以分开出来,那为什么第二组全部缺失要放在第二组部分缺失前面呢?因为全部缺失是部分缺失的特例,而且它们的处理方式是不一样的,所以必须先单独出来,不然全部缺失的特例就没有办法解决了。

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(n * sizeof(int));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;
	while (gap < n)
	{
		//gap个数据和gap个数据进行排序
		for (int j = 0; j < n; j += 2 * gap)
		{
			int begin1 = j; int end1 = j + gap-1;
			int begin2 = j + gap; int end2 = j + 2 * gap - 1;
			int i = j;
			//第一组数据部分缺失
			if (end1 >= n)
			{
				break;
			}
			//第二组数据全部缺失
			if (begin2 >= n)
			{
				break;
			}
			//第二组数据部分缺失
			if (end2 >= n)
			{
				//修正一下
				end2 = n - 1;
			}
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[i++] = a[begin1++];
				}
				else
				{
					tmp[i++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[i++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[i++] = a[begin2++];
			}
			//每次交换一组都拷贝回原数组
			memcpy(a + j, tmp + j, (end2 - j + 1) * sizeof(int));
		}
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

归并排序的特性总结:

1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(N)  , 使用递归的方式也能较好的解决,因为递归的深度并不高

4. 稳定性:稳定

非比较排序之一计数排序:

非比较排序意思是不使用比较的方式进行排序,其中计数排序是其中比较有用的排序。

但是计数排序有一定的局限性:

首先先介绍计数排序的原理:

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤: 1. 统计相同元素出现次数 2. 根据统计的结果将序列回收到原来的序列中

 下面介绍绝对映射和相对映射:

 根据上面的分析,我们不难发现,计数排序是有一定的局限性的,就是所排的数据要相对集中,否则浪费的空间就会很多。

实现的方式:

首先我们要先计算出我们要排的最大和最小值,然后算出他们的差值,因为这个是左闭右闭区间,所以要加一才能计算出接下来要开辟数组的大小。然后遍历数组统计出他们分别出现的次数,并保存到tmp数组中,最后把tmp数组的数据进行还原即可。

下面是代码实现:

void CountSort(int* a, int n)
{
	//计数排序首先要找到最大和最小的相减得到新的数组的个数
	int max = a[0]; int min = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
		if (a[i] < min)
		{
			min = a[i];
		}
	}
	//因为这里是左闭右闭的区间,所以要加一
	int range = max - min + 1;
	int* tmp = (int*)malloc(range * sizeof(int));
	memset(tmp, 0, range * sizeof(int));
	for (int i = 0; i < n; i++)
	{
		tmp[a[i] - min]++;
	}
	//将tmp数组的元素赋值回原数组
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while ((tmp[i])--)
		{
			a[j] = i + min;
			j++;
		}
	}
	free(tmp);
	tmp = NULL;
}

最后总结计数排序:虽然计数排序的时间复杂度是O(N),但是它的使用收到很多的限制,它只适合整形的数据,同时数据之间的差值还要比较小才适用,否则开辟的空间会非常大。

排序算法的总结:

排序中运用最多的是快排。为什么我们要关注排序的稳定性,因为有时的数据要保证数据相对比较有序,例如:在高考都是600分的时候,我们可能就需要按照语文或者其他学科的成绩进行排名。所以我们最好是要保证数据相对稳定。

 对于非比较排序来说使用的并不多。

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值