计数排序-详解
1. 计数排序概述(问题与思想)
-
问题:计数排序是用于对整数序列进行排序,前提是所有待排序的记录都属于区间
[0, k]
。算法通过计算每个元素x
小于x
的记录数,直接将x
放到正确的位置。 -
步骤:
-
步骤 1:计算值为
i
的记录的个数(1 ≤ i ≤ k
),并将其存储在数组num[i]
中。 -
步骤 2:计算小于或等于
i
的记录数(1 ≤ i ≤ k
),并将该信息存储在另一个数组num[i]
中。 -
步骤 3:逆序读取数组
A[n]
,并根据num[i]
数组中的信息,将元素放到新数组B
的正确位置。
-
-
示例序列:
输入数组A[n] = {2, 1, 5, 2, 4, 3, 0, 5, 3, 2}
,且k = 5
。算法通过计算每个元素出现的次数,并将它们放置到新数组B
的正确位置。
2. 为什么逆序读取数组 A[n]
?
-
解释:之所以要逆序读取数组,是因为我们需要根据每个元素小于或等于它的元素个数来确定该元素在数组
B
中的位置。而这一过程不能就地进行,需要使用额外的数组(例如num[i]
)来存储计数值,从而帮助正确地将元素放到B
中。逆序读取确保了当一个元素多次出现时,最后一个出现的元素会被优先放置到前面,这保持了排序的稳定性(即相同值的元素会保持它们原来的相对顺序)。
3. 计数排序算法
算法的具体步骤如下:
-
输入:待排序数组
A[n]
和元素的取值区间最大值k
。 -
输出:排序后的数组
B[n]
。 -
步骤:
-
计算数组
A[n]
中每个元素的出现次数,并将结果存储在num[i]
中。 -
计算小于或等于
i
的元素个数,并将其存储在num[i]
中。 -
逆序读取数组
A[n]
,根据num[i]
中的值将元素放入数组B
中。 -
输出排序后的数组
B[n]
。
-
该算法的时间复杂度为 O(n + k)
,其中 n
是数组 A[n]
的元素个数,k
是元素的最大值。
简而言之,计数排序非常适合用于对整数数据进行排序,尤其是当数据的范围 [0, k]
相对较小而元素数量 n
较大的情况。但当数据范围非常大时,计数排序则不适用。
下面是实现计数排序的完整代码:
计数排序代码(Python实现)
def counting_sort(A):# Step 1: 找到数组中的最大值 k,定义 num 数组大小为 k+1k = max(A)num = [0] * (k + 1) # 记录每个元素出现的次数B = [0] * len(A) # 存储排序后的结果# Step 2: 统计每个元素出现的次数for i in range(len(A)):num[A[i]] += 1# Step 3: 计算每个元素小于等于它的个数for i in range(1, len(num)):num[i] += num[i - 1]# Step 4: 逆序读取数组 A,根据 num 数组中的信息,将元素放入 B 中for i in range(len(A) - 1, -1, -1):B[num[A[i]] - 1] = A[i]num[A[i]] -= 1return B# 示例
A = [2, 1, 5, 2, 4, 3, 0, 5, 3, 2]
print("原数组:", A)
sorted_A = counting_sort(A)
print("排序后的数组:", sorted_A)
代码解释:
-
输入数组
A
:我们要排序的数组,A[n] = {2, 1, 5, 2, 4, 3, 0, 5, 3, 2}
。 -
k
的计算:首先,我们计算数组A
中的最大值k
,用于确定计数数组num
的大小。因为计数排序是基于元素的大小范围来处理的。 -
num
数组:用于存储每个元素的出现次数。num[i]
存储的是元素i
在数组A
中出现的次数。 -
num[i]
数组的累加:我们通过累加计算每个元素小于或等于它的个数。这样,num[i]
就表示了数组中小于或等于i
的元素个数。 -
逆序填充数组
B
:在排序过程中,我们从数组A
的末尾开始读取,按照num
数组的计数信息,逐一将元素放入新的数组B
中。 -
返回排序后的数组
B
。
输出示例:
原数组: [2, 1, 5, 2, 4, 3, 0, 5, 3, 2]
排序后的数组: [0, 1, 2, 2, 2, 3, 3, 4, 5, 5]
总结:
-
计数排序的核心思想是:首先统计每个元素的出现次数,然后计算每个元素小于或等于它的元素个数,最后通过反向填充的方式将元素放到正确的位置。
-
时间复杂度:
O(n + k)
,其中n
是数组的长度,k
是数组元素的最大值。 -
空间复杂度:
O(k)
,需要额外的数组来存储计数信息。