排序算法4-归并排序、非比较排序

1.归并排序

思想:建立在归并操作上的一种有效的排序算法,是采用分治法的一个非常典型的应用,将已经有序的子序列合并得到完全有序的序列,即先使每个子序列有序,再使子序列合并,得到完全有序的序列。这就需要递归,并且把数组平均分为两个子数组,每个子数组的元素可能不会平分,但是我们不用管这个东西,只要知道最终要出现left==right的时候就结束递归,后面再进行排序,思想很简单,代码如下:

//分解数组
void _MergeSort(int* arr, int left, int right, int* tmp)
{
	//这个tmp是新数组
	if (left >= right)
	{
		return;
	}
	//求中间值的时候如果直接把二者加起来会导致最后溢出导致结果出错,所以不能直接加起来
	int cha = left - right;
	int mid = right + cha / 2;
	if (cha < 0)
	{
		cha = right - left;
		//或者写为cha*=-1
		mid = left + cha / 2;
	}
	//根据mid划分成两个序列
	//[left,mid][mid+1,right]
	_MergeSort(arr, left, mid, tmp);
	_MergeSort(arr, mid + 1, right, tmp);
	//合并
	int begin1, end1, begin2, end2;
	begin1 = left;
	end1 = mid;
	begin2 = mid + 1;
	end2 = right;
	int index = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])
		{
			tmp[index++] = arr[begin1++];
		}
		else
		{
			tmp[index++] = arr[begin2++];
		}
	}
	//可能没有插入完全
	while (begin1 <= end1)
	{
		tmp[index++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}
	//放回去
	for (int i = left; i <= end2; i++)
	{
		//不能用begin1因为此时begin1已经改变了
		arr[i] = tmp[i];
	}
}
//归并排序
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}
int main()
{
	int a[] = { 5,3,4,7,3,7,8,2,6,2,1,3,4,9,10,5,6,32,54,48 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序之前: ");
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]); 
	}
	printf("\n排序之后: ");
	MergeSort(a, n);
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

最终结果如下:

2.计数排序

又称为鸽巢原理,是对哈希直接定址法的变形,步骤:1、统计相同元素出现的次数;2、根据统计的结果将序列回收到原来的序列中。我们把每个相同元素出现的次数放入一个数组中,用下标表示。

我们应该如何确定另外一个数组的大小呢?

我们找出这个待排序数组的最大最小值,相减后得到的就是数组的大小-1,因为我们要使用下标来表示,所以我们需要的是加1的下标,所以元素也要加一!

最终代码如下:

//计数排序
void CountSort(int* arr, int n)
{
	int min = arr[0];
	int max = arr[0];
	int index = 0;
	for (int i = 1; i < n; i++)
	{
		//因为我们已经有arr[0]==max==min,所以没必要比较
		if (arr[i] > max)
		{
			max = arr[i];
		}
		if (arr[i] < min)
		{
			min = arr[i];
		}
	}
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail\n");
	}
		memset(count, 0, sizeof(int) * range);
	//以上代码可以用calloc(全部初始化为0)
	for (int i = 0; i < n; i++)
	{
		count[arr[i] - min]++;
	}
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			arr[index++] = min + i;
		}
	}
	free(count);
	count = NULL;
}
int main()
{
	int a[] = { 5,3,4,7,3,7,8,2,6,2,1,3,4,9,10,5,6,32,54,48 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序之前: ");
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]); 
	}
	printf("\n排序之后: ");
	//MergeSort(a, n);
	CountSort(a, n);
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

最终结果如下:

如果最大值和最小值相差过大,这个算法就会非常浪费空间,所以这个算法的适用范围不广,主要是用其他算法。至于一些看不懂的函数需要各位去查找资料了哦!

3.总结

排序算法是生活中常用的算法,学好它很重要,每一个算法都有他们自己的优势和适用地方,如:快速排序在对于已经排好序的数组会表现出过高的复杂度,而一些重复数据也可能导致快速排序算法的时间复杂度升高,但是这个快速排序只针对那些我们已经学过的,其他的算法就不一定了。至此,所有数据结构的知识已经全部讲完,下节我将开始讲解C++入门,我暂时还没开始学C++,所以下一篇可能需要下周才能更新了,喜欢的话可以一键三连哦!