排序算法下界

对任意的比较排序算法都可以抽象为一棵决策树,它是一棵完全二叉树,可以表示在给定输入规模下对所有元素的比较操作,其中,控制、数据移动等其它操作都被忽略。对于一个正确的排序算法,它都能够生成输入的每一种排列,所以对一个正确的比较排序算法来说,n个元素的$n!$种可能的排列都应该出现在决策树的叶结点上,对于一个给定的输入,其排序结果必然就是这所有可能中的某一个。

根据这个比较排序的决策树模型,从根结点到一个可达的叶结点之间的最长简单路径的长度,表示的是对应排序算法中最坏情况下的比较次数,它等于决策树的高度。因此,当决策树中的每种排列都是以可达的叶结点形式出现时,该决策树高度的下界也就是比较排序算法运行时间的下界。

最坏情况的下界

考虑一棵高度为h,具有l个可达叶结点的决策树,它对应一个对n个元素所做的比较排序,输入的$n!$种可能的排序都是叶结点,并且一棵高度为h的二叉树中,叶结点的数量不多于$2^h$,因此有: $$ n! \leq l \leq 2^h $$ 得到 $$ h \geq \lg (n!) = \Omega (n\lg n) $$

计数排序

适用场景

n个输入元素是0到k区间的整数,或者元素可以通过转换函数,在不丢失大小信息的情况下,唯一地映射到0到k区间某一个整数。

基本思想

对每一个输入元素x确定小于x的元素个数,利用这一信息可以直接将x放到它在输出数组中的位置上。

实现

def counting_sort_integer(A, B, k):
    C = [0 for x in xrange(0, k+1)]
    for x in A:
        C[x] += 1
    for i in xrange(0, k+1):
        if i > 0:
            C[i] += C[i-1]
    for x in A:
        # index start with 0, so minus 1
        B[C[x]-1] = x
        C[x] -= 1

if __name__ == '__main__':
    A = [123, 21, 14321, 0, 120, 98, 1000, 1024, 123, 1, 0, 123, 24, 123, 3, 2, 12]
    B = [None] * len(A)
    counting_sort_integer(A, B, 14321)
    print B

基数排序

适用场景

对具有多关键字域的记录或某一基数表示的数(二进制位表示,10进制数,16进制数,字符串等)进行排序,其共同的特点是每个元素有d位,每一位有一个固定的取值区间,因此可以利用到计数排序的方法。

基本思想

对于有n个d位元素的输入,从每个元素的最低有效位开始,到最高有效位,使用一个稳定的排序算法对n个元素进行排序。一个稳定的排序算法指对相等元素的排序不改变它们已有的顺序。

稳定的排序

计数排序可以很容易做到稳定排序,只需在使用计数序号将输入元素放入输出元素时,先放入输入序列中靠后的元素即可,因此上面的计数排序的稳定版本为:

def counting_sort_integer(A, B, k):
    C = [0 for x in xrange(0, k+1)]
    for x in A:
        C[x] += 1
    for i in xrange(0, k+1):
        if i > 0:
            C[i] += C[i-1]
    for j in xrange(len(A)-1, -1, 0):
        x = A[j]
        # index start with 0, so minus 1
        B[C[x]-1] = x
        C[x] -= 1

其它排序算法也可以设计为稳定的排序,但可能需要额外的的时间和空间复杂度,这也是算法导论中的一道练习题。

实现

def counting_sort_char(index, A, B, k):
    """
    i: the digit index for comparation
    """
    C = [0 for x in xrange(0, k+1)]
    for x in A:
        # digit index exceed digit length
        if index >= len(x):
            C[0] += 1
        else:
            C[ord(x[index])] += 1
    for i in xrange(0, k+1):
        if i > 0:
            C[i] += C[i-1]
    # Now C[i] contains the number of element smaller or equal to i,
    # which i is "index"th digit of element of A
    for j in xrange(len(A)-1, -1, -1):
        x = A[j]
        i = 0
        if index < len(x):
            i = ord(x[index])
        # index start with 0, so minus 1
        B[C[i]-1] = x
        C[i] -= 1

def radix_sort_string(A, d):
    size = len(A)
    for i in xrange(0, d):
        B = [None] * size
        counting_sort_char(i, A, B, 255)
        A = B
        print A
    return A

if __name__ == '__main__':
    A = ["a", "bc", "def", "c", "hello world", "asdasdf", "code", "python", "081324asdf", "1", "shark-"]
    d = 1
    for e in A:
        length = len(e)
        if length > d:
            d = length
    B = radix_sort_string(A, d)
    print B

桶排序

适用场景

输入数据服从均匀分布,它由一个随机过程产生,该过程将元素均匀、独立地分布在[0, 1)区间上。

基本思想

输入序列的大小为n,桶排序将[0, 1)区间划分为n个相同大小的子区间,也称这些子区间为桶。然后将n个输入数分别放到各个桶中。因为输入数据是均匀分布的,一般不会出现很多元素落入同一个桶中的情况。将所有元素都放入相应的桶中后,对每个桶中的元素进行排序,然后按顺序遍历所有桶中的元素即可。

实现

def swap(A, i, j):
    temp = A[i]
    A[i] = A[j]
    A[j] = temp

def bubble_sort(A):
    for i in xrange(len(A) - 1, -1, -1):
        max = A[i]
        for j in xrange(i - 1, -1, -1):
            if A[j] > max:
                max = A[j]
                swap(A, i, j)

def bucket_sort(A):
    n = len(A)
    min_v = min(A)
    max_v = max(A)
    range = max_v - min_v
    range += range / n
    bucket = [[] for _ in xrange(0, n)]
    for x in A:
        i = int((x - min_v) / range * n)
        bucket[i].append(x)
    current = 0
    for i in xrange(0, n):
        bubble_sort(bucket[i])
        for x in bucket[i]:
            A[current] = x
            current += 1

if __name__ == '__main__':
    A = [57, 35, -100.2, -12, -20, -23.123, 0, -12, -20, -23.123, 0, 1, 23, 214, 123, 135]
    bucket_sort(A)
    print A