基于比较大排序算法至少需要O(n*lgn)的复杂度。对于一些特殊的输入我们有一些特殊的算法,有可能得到复杂度为O(n)的算法。本文简要描述其中的几种:count排序,基数排序和桶排序。
count排序的假设是被排序的字段在一个有限的范围内,比如对1000人按照年龄排序。
count排序需要两个额外的变量,一个用于存放排序结果,长度是n,另一个用于存放中间结果,长度是k。其中n为输入的长度,k-1为输入数据的最大值。
设输入A[0..n-1],初始化变量 B[0..n-1], C[0..k-1],算法如下:
for i = 0 .. k-1 C[i] = 0 |
for j = 0 .. n-1 C[A[j]] ++ |
for i = 1 .. k-1 C[i] = C[i-1] + C[i] |
for j = n-1 .. 0 B[C[A[j]]-1] = A[j] C[A[j]] -- |
容易证明这个算法复杂度为O(n+k)。考虑到k一般为O(n),所以整体复杂度为O(n)。
基数排序的起源是扑克牌的排序,先按照花色排序,再按照数字排序。如果要对一些10进制数排序,比如:123,456,321,789.可以按照从低位到高位的顺序,先第一位排序,再第二位排序,依次类推。需要注意两点:
1. 必须先低位后高位,因为高位更有决定权,只有当高位一样的时候,才按照低位到顺序排序,这就是第二点:
2. 按照某位排序时,排序算法必须是稳定的;
算法很简单:
for i = 1..b
按照第i位排序,稳定排序
很容易看到这个复杂度是O(b*(n+k)),其中n是输入元素个数,k是输入元素在某个位上元素个数。b是输入元素的位数。
稍微变化一下,不是每位排序一次,而是分成若干组,每组有r位,那么复杂度为O(b/r * (n+2^r))。以r为变量,对这个表达式求最小值。分几种情况:
1. b<lgn,由于r小于b,故2^r小于n,因此复杂度为O(n)
2. b>=lgn,取r=lgn,复杂度也是O(n)
桶排序
桶排序更类似于count排序的连续版本。count排序需要输入是整数。桶排序假设输入在0~1区间均匀分布。假设输入A[1..n],满足A[i]小于1,大于等于0,后我们把该0~1等分成n份,每份是一个bucket。然后用B数组[1..n]分别存放对应区间的A的元素,并对这些元素使用插入排序。最后将B的各个元素对应的list串联起来。比较麻烦的是这个算法的复杂度证明。结论是如果输入的分布满足均匀分布,那么复杂度为O(n).