Python实现 《算法导论 第三版》中的算法 第8章 线性时间排序

27 篇文章 0 订阅
21 篇文章 0 订阅

第8章 线性时间排序

8.1 排序算法的下界
  1. 归并排序、堆排序和快速排序能在 O ( n l g n ) O(nlgn) O(nlgn)时间内排序 n n n个数。归并排序和堆排序在最坏情况下就能够达到该时间,快速排序在平均情况达到该时间(快速排序最坏情况下是 O ( n 2 ) O(n^2) O(n2))。
  2. 如果在排序中各元素的次序依赖于它们之间的比较,就把该类排序算法称为比较排序。上述三种排序算法和冒泡排序、插入排序、选择排序等都是比较排序。
  3. 本节介绍了决策树模型。比较排序可以被抽象为一棵决策树。该模型证明了:对于包含n个元素的输入序列来说,在最坏情况下,任何比较排序都需要做 Ω ( n l g n ) \Omega(nlgn) Ω(nlgn)次比较。因此,归并排序和堆排序是渐近最优的比较排序算法。
  4. 8.2节、8.3节和8.4节介绍了三种线性时间复杂度的排序算法:计数排序、基数排序和桶排序。这些算法是用运算而不是比较来确定排序顺序的。因此,下界 Ω ( n l g n ) \Omega(nlgn) Ω(nlgn)对它们是不适用的。
8.2 计数排序
  1. 计数排序(counting sort)假设 n n n个元素中的每一个都是在 0 0 0 k k k区间内的一个正数,其中 k k k为某个整数。当 k = O ( n ) k=O(n) k=O(n)时,排序的运行时间为 Θ ( n ) \Theta(n) Θ(n)
  2. 计数排序的基本思想是:对每一个输入元素 x x x,确定小于 x x x的元素个数。利用这一信息,就可以直接把 x x x放到它在输出数组中的位置上了。当有几个元素相同时,这一方案就要略作修改,因为不能把它们放在同一个输出位置上。
  3. 在计数排序算法的代码中,假设输入是一个数组 A [ 1... n ] A[1...n] A[1...n] A . l e n g t h = n A.length=n A.length=n。另外还需要两个数组: B [ 1... n ] B[1...n] B[1...n]存放排序的输出, C [ 0... k ] C[0...k] C[0...k]提供临时存储空间。
  4. 计数排序的时间代价为 Θ ( k ) + Θ ( n ) = Θ ( k + n ) \Theta(k)+\Theta(n)=\Theta(k+n) Θ(k)+Θ(n)=Θ(k+n)。当 k = O ( n ) k=O(n) k=O(n)时,我们一般会采用计数排序,此时的运行时间为 Θ ( n ) \Theta(n) Θ(n)。计数排序的下界优于8.1节中证明的 O ( n l g n ) O(nlgn) O(nlgn),它不是一个比较排序算法。
  5. 如下的代码能保证计数排序是稳定的,因为第12行表示从数组最后一个元素逐一向前开始排序。如果换成第13行中注释的语句,表示从数组第一个元素逐一向后排序,则计数排序是不稳定的。
  6. 保证计数排序的稳定性的另一个原因是:计数排序经常会被用作8.2节中基数排序算法的一个子过程。为了使基数排序正确运行,计数排序必须是稳定的。
class CountSort:
    def sort(self, A, k):
        C = [0] * (k + 1)
        for j in range(len(A)):
            # C[i] contains the number of elements equal to i
            C[A[j]] = C[A[j]] + 1
        for i in range(1, k+1):
            # C[i] contains the number of elements less than or equal to i
            C[i] = C[i] + C[i-1]
            
        B = [0] * len(A)
        for j in range(len(A)-1, -1, -1): # stable sort
#        for j in range(len(A)): # unstable sort
            B[C[A[j]]-1] = A[j]
            C[A[j]] -= 1
        for i in range(len(A)):
            A[i] = B[i]


def test():
    cs = CountSort()
    A = [2, 5, 3, 0, 2, 3, 0, 3] # P109例子
    cs.sort(A, 5)
    print(A)


if __name__ == '__main__':
    test()
8.3 基数排序
  1. 基数排序(radix sort)用于对 n n n张卡片上的 d d d位数进行排序。与人们直观感受相悖的是,基数排序是先按最低有效位进行排序来解决卡片排序问题的。然后将所有卡片合成一叠,用同样的方法按次低有效位对所有卡片进行排序,并把排好的卡片再次合成一叠。重复这一过程 d d d轮( d d d位数字)后,卡片可以排好序。
  2. 为了保证基数排序的稳定性,选择计数排序作为卡片的排序方法。
  3. 基数排序的代码是非常直观的,使用一个稳定排序方法从最低位开始到最高位进行排序。假设输入是一个数组 A [ 1... n ] A[1...n] A[1...n],其中每一个元素都是一个 d d d位数。第1位表示最低位,第 d d d位表示最高位。
  4. 稍微修改了计数排序,数组 B B B表示数组 A A A中各元素的每一位,简化了计算每一轮排序后数组 A A A的有效位。
  5. 给定 n n n d d d位数,其中每一个数位有 k k k个不同的取值。如果基数排序使用的稳定排序方法(计数排序)耗时 Θ ( n + k ) \Theta(n+k) Θ(n+k),那么它就可以在 Θ ( d ( n + k ) ) \Theta(d(n+k)) Θ(d(n+k))时间内将这些数排好序。当 d d d为常数且 k = Θ ( n ) k=\Theta(n) k=Θ(n)时,基数排序具有线性的时间代价 O ( n ) O(n) O(n)
class RadixSort:
    def sort(self, A, d):
        temp = A.copy()
        for i in range(d):
            temp = [t // 10 for t in temp]
            self.__count_sort(A, 9, temp)
    
    def __count_sort(self, A, k, B):
        ''' modified count_sort
        A and B have equal length. And B is used to compute every column of A.
        '''
        keys = [num % 10 for num in B]
        C = [0] * (k + 1)
        for j in range(len(keys)):
            # C[i] contains the number of elements equal to i
            C[keys[j]] = C[keys[j]] + 1
        for i in range(1, k+1):
            # C[i] contains the number of elements less than or equal to i
            C[i] = C[i] + C[i-1]
        # sort A and B    
        tempA = [0] * len(A)
        tempB = [0] * len(B)
        for j in range(len(A)-1, -1, -1): # stable sort
            tempA[C[keys[j]]-1] = A[j]
            tempB[C[keys[j]]-1] = B[j]
            C[keys[j]] -= 1
        for i in range(len(A)):
            A[i] = tempA[i]
            B[i] = tempB[i]
   
         
def test():
    rs = RadixSort()
    A = [329, 457, 657, 839, 436, 720, 355] # P110例子
    rs.sort(A, 3)
    print(A)    
    

if __name__ == '__main__':
    test()
8.4 桶排序
  1. 桶排序(bucket sort)假设输入数据服从均匀分布,平均情况下它的时间代价为 O ( n ) O(n) O(n)。与计数排序类似,因为对输入数据作了某种假设,桶排序的速度也很快。具体来说,计数排序假设输入数据都属于一个小区间内的整数,而桶排序则假设输入数据由一个随机过程产生,该过程将元素均匀、独立地分布在 [ 0 , 1 ) [0,1) [0,1)区间内。
  2. 桶排序将 [ 0 , 1 ) [0,1) [0,1)区间划分为 n n n个相同大选哦的子区间,或称为桶。然后将 n n n个数字分别放到各个桶中。因为输入数据均匀分布,所以一般不会出现很多数落在同一个桶中的情况。为了得到输出结果,我们先对每个桶中的数进行排序,然后遍历每个桶,按照次序把各个桶中的元素列出来即可。
  3. 在桶排序的代码中,我们假设输入是一个包含 n n n个元素的数组 A A A,每个元素位于 [ 0 , 1 ) [0,1) [0,1)区间。此外,算法中需要一个临时数组 B [ 0... n − 1 ] B[0...n-1] B[0...n1]来存放桶。桶可以用数组或链表来实现。
  4. 桶排序的期望运行时间是 Θ ( n ) \Theta(n) Θ(n)。即使输入数据不服从均匀分布,桶排序也仍然可以在线性时间内完成。只要输入数据满足下列性质:所有桶的大小的平方和与总的元素个数成线性关系。
class BucketSort:
    def sort(self, A):
        import math
        n = len(A)
        B = []
        for i in range(n):
            B.append([])
        for i in range(n):
            B[math.floor(n*A[i])].append(A[i])
        for i in range(n):
            B[i].sort() # 可以使用插入排序
            
        i = 0
        for j in range(n):
            for num in B[j]:
                A[i], i = num, i+1
        
        
def test():
    bs = BucketSort()
    A = [0.78, 0.17, 0.39, 0.26, 0.72, 0.94, 0.21, 0.12, 0.23, 0.68] # P112例子
    bs.sort(A)
    print(A)
    

if __name__ == '__main__':
    test()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值