Bootstrap

【数据结构与算法】排序算法(下)——计数排序与排序总结

写在前面

书接上文:【数据结构与算法】排序算法(中)——交换排序之快速排序

文章主要讲解计数排序的细节与分析源码。之后进行四大排序的总结。



一、计数排序(非比较排序)

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

操作步骤:

  1. 确定范围:首先,确定待排序数组中元素的取值范围,即最小值和最大值。假设数据的值在 [0, K-1] 范围内。

  2. 创建计数数组:根据元素的取值范围创建一个计数数组count,该数组的每个索引代表数组中某个元素的值,值为该元素出现的次数。例如,count[i] 表示元素i出现的次数。

  3. 统计元素出现次数:遍历原始数组,对每个元素x,将 count[x] 增加 1。这样,count 数组就记录了每个元素的出现次数。

  4. 重建排序数组:遍历count数组,根据每个元素的出现次数将元素放入最终的排序数组中。

在这里插入图片描述
假设有一个待排序数组[4, 2, 2, 8, 3, 3, 1]

步骤 1:确定数据范围
最小值1最大值8,因此我们需要创建一个长度为 8 的计数数组 count,用于记录 18 每个元素的出现次数

步骤 2:设计计数数组
步骤1中确定了数组开辟长度为 8。在8个长度中,我们应该把下标0就设置存储最小元素出现的次数,接下来的下标就对应着当前数据-最小元素的下标

  • 举个例子:
    在当前数组中:1是最小元素,那么计数数组的0下标就对应着统计1元素出现的次数,那么元素8对应的小标就是8-1的位置,即下标7才是计算元素8出现的次数。

步骤 3:统计每个元素的出现次数
遍历原始数组 [4, 2, 2, 8, 3, 3, 1],统计每个元素出现的次数,并更新计数数组:

  • 数字 4count[4-1] = count[3] 增加 1count[3] = 1
  • 数字 2count[2-1] = count[1] 增加 1count[1] = 1
  • 数字 2count[2-1] = count[1] 增加 1count[1] = 2
  • 数字 8count[8-1] = count[7] 增加 1count[7] = 1
  • 数字 3count[3-1] = count[2] 增加 1count[2] = 1
  • 数字 3count[3-1] = count[2] 增加 1count[2] = 2
  • 数字 1count[1-1] = count[0] 增加 1count[0] = 1

统计完成后,count 数组变为:count = [1, 2, 2, 1, 0, 0, 0, 1]
在这里插入图片描述
这表示

  • 数字 1 出现了 1
  • 数字 2 出现了 2
  • 数字 3 出现了 2
  • 数字 4 出现了 1
  • 数字 5 没有出现
  • 数字 6 没有出现
  • 数字 7 没有出现
  • 数字 8 出现了 1

步骤 4:根据计数数组重建排序数组
接下来,我们可以根据 count 数组来重建排序数组:

从 count 数组中取出每个数字出现的次数,然后把它们按顺序放入结果数组中。

  • count[0] = 1,因此排序数组中加入一个 1

  • count[1] = 2,因此排序数组中加入两个2

  • count[2] = 2,因此排序数组中加入两个 3

  • count[3] = 1,因此排序数组中加入一个 4

  • count[4] = 0,跳过。

  • count[5] = 0,跳过。

  • count[6] = 0,跳过。

  • count[7] = 1,因此排序数组中加入一个 8

重建后的原数组: [1, 2, 2, 3, 3, 4, 8]
在这里插入图片描述

代码的实现:
void countingSort(int* arr, int len) {
	int min = arr[0], max = arr[0];//

	for (int i = 0; i < len; i++) {//找最大最小值
		if (arr[i] > max) {
			max = arr[i];
		}
		if (arr[i] < min) {
			min = arr[i];
		}
	}
	
	int* countArr = (int*)calloc((max - min) + 1, sizeof(int));//创建一个计数数组,用于记录每个数字出现的次数。
	
	for (int i = 0; i < len; i++) {//计数
		countArr[arr[i] - min]++;
	}
	int j = 0;
	for (int i = 0; i < (max - min) + 1; i++) {//排序
		/*if (countArr[i] <= 0) {
			;
		}
		else {
			arr[j++] = i + min;
			countArr[i]--;
			i--;
		}*/
		while (countArr[i]--) {
			arr[j++] = i + min;
		}
	}
}
  • 需要注意的细节在最小值与最大值的定义时,需要初始化为数组中的元素,只有这样才会对排序不会造成影响。
  • 在排序中,可以设计一个循环来进行辅助排序,也可以使用条件判断语句排序,这二者的时间复杂度都是一样的
  • 计数循环中,使用了arr[i] - min,这样是min最小的值设置为数组的开头,即下标为0的位置,这样就可以避免计数数组开辟过多的空间,也可以更好的适配含负数的数据排序

计数排序的特性总结:

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

二、排序总结

2.1、稳定性

在不才写的三篇排序笔记中,不才在每个排序中都有写特性总结,这其中有稳定性,在排序中,稳定性不是说是这个排序是否每次都稳定排序,而是指在排序过程中,若两个元素的值相等,它们在排序后的相对顺序保持不变。
在这里插入图片描述
我们排序 { 1 2 3 1 4 } 这组数据,在这组数据中,如果我们使用的是稳定的排序排序完成后红色的1一定会保持在橙色的1后面,如:{ 1 1 2 3 4 }如果不能确保每次排序红色的1在橙色的1后面,那就是不稳定排序如:{ 1 1 2 3 4 }


3.2、排序算法复杂度及稳定性总结

在这里插入图片描述

在这里插入图片描述


ps:
排序算法(上)——插入排序与选择排序
排序算法(中)——交换排序与归并排序

以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 如果对大家有帮助的话,就请多多为我点赞收藏吧~~~💖💖
请添加图片描述

ps:表情包来自网络,侵删🌹

;