排序(5) 线性时间排序

前面所说过的直接插入排序、简单选择排序、冒泡排序、希尔排序、归并排序、堆排序、快速排序都是通过比较关键字大小来确定最终序列的,称之为基于比较的排序。任何基于比较的排序在最坏情况下运行时间都至少是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)。所以说,数据服从平均分布对于桶排序的性能来说是很重要的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值