[数据结构]归并排序、计数排序

文章目录

  • 归并排序
    • 归并排序思想
    • 归并排序的实现
  • 计数排序
    • 计数排序思想
    • 归并排序的实现

归并排序

归并排序思想

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

归并排序的思想类似于二叉树的后序遍历操作,归并排序就是先把左半边数组排好序,再把右半边数组排好序,然后把两半数组合并,我们可以把归并排序的过程想象成一棵树:

[数据结构]归并排序、计数排序_第1张图片

在每个节点的后序位置(左右子节点已经被排好序)的时候执行 merge 函数,合并两个子节点上的子数组:

[数据结构]归并排序、计数排序_第2张图片

归并排序的实现

归并排序体现了 “分而治之” 的算法思想,具体为

  • 「分」: 不断将数组从 中点位置 划分开,将原数组的排序问题转化为子数组的排序问题;
  • 「治」: 划分到子数组长度为 1 时,开始向上合并,不断将 左右两个较短排序数组 合并为 一个较长排序数组,直至合并至原数组时完成排序;

如下图所示,为数组 [7,3,5,1,6,4,8,2] 的归并排序过程:

[数据结构]归并排序、计数排序_第3张图片
基本步骤:

  1. 递归划分

首先计算数组的中点mid,递归划分左子数组_MergeSort(a, left, mid)和递归划分右子数组_MergeSort(a, mid+1, right),当left >= right时,停止划分,开始归并子数组

if (left >= right)
		return;

int mid = (left + right) >> 1;
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid + 1, right, tmp);
  1. 合并子数组

设置双指针begin和end分别指向左/右子数组的首元素和尾元素,每次较小的元素放进tmp数组中,归并完毕,将tmp数组中的数组拷贝回原数组

int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int i = left;
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++];
}

for (int j = left; j <= right; ++j)
{
	a[j] = tmp[j];
}

完整归并排序参考代码:

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)
		return;

	// 划分区间
	int mid = (left + right) >> 1;
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);

	// 合并子数组
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = left;
	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++];
	}

	for (int j = left; j <= right; ++j)
	{
		a[j] = tmp[j];
	}
}

// 归并排序
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		printf("malloc failed\n");
		exit(-1);
	}

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

	free(tmp);
	tmp = NULL;
}

归并排序的特性总结:

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

计数排序

计数排序思想

计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+range)(其中range是整数的范围),快于任何比较排序算法。 当然这是一种牺牲空间换取时间的做法,而且当O(range)>O(n*log(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如归并排序,堆排序)

动图演示:

[数据结构]归并排序、计数排序_第4张图片

归并排序的实现

基本步骤:

  1. 找出序列中的最大值max和最小值min
  2. 计算辅助数组count的大小range = max - min + 1,malloc申请辅助数组空间
  3. 遍历待排序数组,将待排列数组的值减去min的值对应count数组的下标的值累加一次
  4. 遍历count数组,将数组中不为零的数加上min的值拷贝回原数组中,排序结束

参考代码:

// 计数排序
void CountSort(int* a, int n)
{
	int min = a[0], max = a[0];
	for (int i = 1; i < n; ++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);
	memset(count, 0, sizeof(int) * range);
	if (count == NULL)
	{
		printf("malloc failed\n");
		exit(-1);
	}

	for (int i = 0; i < n; ++i)
	{
		count[a[i] - min]++;
	}

	int j = 0;
	for (int i = 0; i < n; ++i)
	{
		while (count[i]--)
		{
			a[j++] = i + min;
		}
	}
}

计数排序的特性总结

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  2. 时间复杂度:O(MAX(N,range))
  3. 空间复杂度:O(range)
  4. 稳定性:稳定

你可能感兴趣的:(数据结构,排序算法,算法)