前面所说过的直接插入排序、简单选择排序、冒泡排序、希尔排序、归并排序、堆排序、快速排序都是通过比较关键字大小来确定最终序列的,称之为基于比较的排序。任何基于比较的排序在最坏情况下运行时间都至少是O(nlgn)级的。本节介绍几个基于计算而非比较的排序算法,其期望时间为O(n),但是适用场景有限且需要借助额外空间,它们分别是计数排序、基数排序、桶排序。
计数排序
假设数组中n个元素都是0-k之间的整数,对于数组里每个元素来说,如果能知道数组里有多少项小于或等于该元素,就能准确地给出该元素在排序后的数组中的位置。其基本过程如下图所示。
这里我们可以通过查找数组中最大值来确定k值,算法实现如下:
#既然要找到最大值,那一并把最小值也找出来吧。O(n)
def FindMaxMin(A,first,last):
if first <= last:
maxElem = minElem = A[first]
for i in range(first+1,last+1):
if maxElem < A[i]:
maxElem = A[i]
elif minElem > A[i]:
minElem = A[i]
else:
pass
return maxElem, minElem
#计数排序
def CountingSort(A,first,last):
if first <= last:
maxElem,minElem = FindMaxMin(A,first,last)
C = []
B = A[:] #把A中的元素复制到B
for i in range(maxElem+1): #初始化C数组
C.append(0)
for i in range(first,last+1): #计算A中各值的元素数目
C[A[i]] += 1
for i in range(1,maxElem+1): #确定A中元素的位置
C[i] += C[i-1]
for i in range(len(B)-1,-1,-1): #由于B和A相等,从B中逆遍历数组复制到A中已确定的位置
A[C[B[i]]-1] = B[i]
C[B[i]] -= 1
上述实现基于一个比较强的假设,元素都是非负整数,如果有负整数的情况下怎么办呢?解决办法其实也很简单,把数组所有元素都减去最小值,转化成了非负整数的情况,然后计数排序,最后的结果再加上原来的最小值,即为最终结果。
#加强版计数排序
def CountingSortStrong(A,first,last):
if first <= last:
maxElem,minElem = FindMaxMin(A,first,last)
for i in range(first,last+1):
A[i] -= minElem
CountingSort(A,first,last)
for i in range(first,last+1):
A[i] += minElem
实际工作中,当k = O(n)时,一般可以采用计数排序。另外计数排序是稳定的,由于计数排序经常被用作基数排序算法的一个子过程,只有是稳定的才能使基数排序结果正确。
基数排序
基数排序过程,对各数从低位开始对每一分位排序,如先按照个位值大小排序(排序过程中要保持稳定性),再按照十位值大小排序,以此类推,直到对每一分位都已经排好序。
过程如下图:
这里对于棘突实现,我们不按照十进制个位十位来排序,而是按照计算机二进制按bit位来依次排序。
#按位获取掩码
def GetMask(nBit):
return ~(~0<<nBit)
#对于一个非负整数,获取它的比特位数,即把它换成二进制有多少位
def GetBitCount(v):
bitCount = 0
while v > 0 :
bitCount += 1
v >>= 1
return bitCount
#基于二进制的按位计数排序
def CountingSortBasedBit(A,first,last,bitStart,mask):
C = []
B = A[first : last+1]
for i in range(mask+1):
C.append(0)
for i in range(first,last+1):
maskNum = (A[i]>>bitStart) & mask
C[maskNum] += 1
for i in range(1,mask+1):
C[i] += C[i-1]
for i in range(last,first-1,-1):
maskNum = (B[i]>>bitStart) & mask
A[C[maskNum]-1] = B[i]
C[maskNum] -= 1
#基数排序,默认按照8位分割数字依次排序
def RadixSort(A,first,last, nBit=8):
if first <= last:
maxElem,minElem = FindMaxMin(A,first,last)
for i in range(first,last+1):
A[i] -= minElem
maxElem,minElem2 = FindMaxMin(A,first,last)
maxBitCount = GetBitCount(maxElem)
mask = GetMask(nBit)
for bitStart in range(0,maxBitCount,nBit):
CountingSortBasedBit(A,first,last,bitStart,mask)
for i in range(first,last+1):
A[i] += minElem
基数排序是稳定的,时间复杂度是线性的,但是需要O(n)的辅助空间。
桶排序:
对于输入数据服从均匀分布,可以使用桶排序。这也是一种性能基于概率的排序。简单来说,比如待排序集合的元素取值范围在0-k之间,且服从均匀分布,那么我们可以给出带顺序编号的m个桶,把[0,k/m)之内的元素放入第一个桶中,[k/m,2k/m)之内的元素放入第二个桶中,[2k/m,3k/m)放到第三个桶中,以此类推,直到所有数都放入各自待定的桶中。接下来,再用直接插入排序对桶内的元素排序。最后按照桶的顺序以及桶内元素的顺序依次输出结果。对于数据服从均匀分布,桶排序期望时间为线性级别。然而如果数据不服从均匀分布,但是只要满足所有桶的大小的平方和与总的元素呈线性关系,桶排序仍然可以在线性时间内完成。极端情况,全部元素都落入一个桶内,那必然免不了时间复杂度变为O(n^2)。所以说,数据服从平均分布对于桶排序的性能来说是很重要的。