数据结构—排序算法篇三

本篇博客是归并排序的讲解和一些非比较排序的讲解。

归并排序你可能没怎么听说过,但是你可能听说过有序链表的合并、有序数组的合并。归并排序就是将两个有序数组进行排序,然后在放到新的数组里形成有序。

归并排序

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

那么问题来了,如何让两个数组有序?

用递归分解法:首先把一个数组分成两半,然后去递归左右区间,区间剩下一个数进行比较。

归并排序代码

c 复制代码
//归并排序
void _MergeSort(int* a, int* tmp, int left, int right)
{
	if (left == right)
	return;

	int mid = (left + right) / 2;

	_MergeSort(a, tmp, left, mid);
	_MergeSort(a, tmp, mid + 1, right);

	int i = left;
	int begin1= left, end1= mid;
	int begin2 = mid + 1, end2 = right;
	//归并
	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 + left, tmp + left, (right - left + 1)*sizeof(int));

}

最后一定要归并一次拷贝一次,不然如果不拷贝,原数组就变化不了,从而导致排序失败!

归并排序非递归

原理和递归差不多,只不过是用循环代替递归。

通过循环建立分组,是数据进行归并

如下图:

归并排序非递归代码实现

c 复制代码
//归并排序 非递归实现
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	int gro = 1;
	while (gro < n)
	{
		for (int i = 0;i < n;i += 2 * gro)
		{
			int begin1 = i, end1 = i + gro - 1;
			int begin2 = i + gro, end2 = i + 2 * gro - 1;
			//归并
			if (begin2 >= n)
				break;

			if (end2 >= n)
				end2 = n - 1;

			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));
		}
		gro = gro * 2;

	}

	free(tmp);
	tmp = NULL;
}

最后说一下归并排序的时间复杂度是O(N*logN);空间复杂度是O(N);

非比较排序

非比较排序是一种较为特殊的排序,非比较排序是不通过元素间直接比较来排序的算法,核心优势是在特定数据场景下时间复杂度突破 O (n log n) 下限。

下面来看看三种非比较排序

  1. 计数排序
    适用场景:数据范围 k 远小于元素个数 n,且数据为非负整数。
    核心逻辑:统计每个数值出现的次数,再根据次数依次输出元素。
  2. 桶排序
    适用场景:数据分布均匀,可划分成多个有序 "桶"。
    核心逻辑:将数据分到不同桶中,对每个桶单独排序(可结合比较排序),最后合并所有桶。
  3. 基数排序
    适用场景:数据可按位拆分(如数字、字符串),且每位的取值范围有限。
    核心逻辑:按低位到高位(或反之)依次排序,每一轮用稳定排序(如计数排序)处理当前位。

计数排序

计数排序是非比较排序中最基础、最常用的算法,核心是通过 "统计元素出现次数" 来直接确定每个元素的最终位置,完全不依赖元素间的大小比较。它的优势是在数据范围可控时,能达到 O (n + k) 的线性时间复杂度(n 是元素个数,k 是数据最大值与最小值的差值),但缺点是对数据类型和范围有严格限制。通俗来讲就是统计数组元素出现的次数,再将他们统一排序

计数排序实现

c 复制代码
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 (min > a[i])
		{
			min = a[i];
		}
	}
	//申请空间
	int range = max - min + 1;
	int* count = (int*)calloc(range, sizeof(int));
	if (count == NULL)
	{
		perror("count fail");
		return;
	}
	
	//排序
	for (int i = 0;i < n;i++)
	{
		count[a[i] - min]++;
	}
	int j = 0;
	for (int i = 0;i < range;i++)
	{
		while (count[i]--)
		{
			a[j++] = i + min;
		}
	}
}

最后总结 计数排序不适用空间过大的数据排序,不适用浮点数排序

基数排序

基数排序(Radix Sort)是基于 "位" 排序的非比较排序,核心是按数字的每一位(或字符的每一位)依次排序,借助稳定排序(如计数排序)保证每一轮排序的有效性。它突破了比较排序的 O (n log n) 下限,时间复杂度为 O (d×(n + k))(d 是最大元素的位数,k 是每一位的取值范围),通用性比计数排序更强。

简单来说比如都是三位数的比较,先比较个位,在比较十位,在比较百位最后让他们有序,日常中有前几大排序 基本用不到基数排序。

桶排序

桶排序(Bucket Sort)是一种非比较排序算法,它通过将数据分到有限数量的有序 "桶" 中,分别对每个桶进行排序,最后合并结果来完成整体排序。

如下图:将他们分别放入桶中在进行排序

最后 非比较排序其实不是太重要,因为有更好的排序可以替代。所以这里简略描述一下。排序三部曲也是结束了,有些排序描述的不太好,因为太抽象了,所以尽力了。

相关推荐
CoovallyAIHub2 小时前
外科医生离手术世界模型还有多远?首次提出SurgVeo基准,揭示AI生成手术视频的惊人差距
深度学习·算法·计算机视觉
t198751282 小时前
基于ELM算法在近红外光谱和拉曼光谱数据处理
算法
xqlily2 小时前
Prover9/Mace4 的形式化语言简介
人工智能·算法
资深web全栈开发2 小时前
二分搜索中 `right = mid` 而非 `right = mid + 1` 的解释
算法·rust·二分搜索
狮子也疯狂3 小时前
基于Django实现的智慧校园考试系统-自动组卷算法实现
python·算法·django
爱coding的橙子4 小时前
每日算法刷题Day84:11.11:leetcode 动态规划9道题,用时2h
算法·leetcode·动态规划
飞鱼&4 小时前
java数据结构
数据结构·二叉树·散列表·红黑树
shenghaide_jiahu4 小时前
字符串匹配和回文串类题目
学习·算法·动态规划
有意义4 小时前
为什么说数组是 JavaScript 开发者必须精通的数据结构?
前端·数据结构·算法