Python实现 《算法导论 第三版》中的算法 第9章 中位数和顺序统计量

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

第9章 中位数和顺序统计量

在一个有 n n n个元素组成的集合中,第 i i i个顺序统计量(order statistic)是该集合中第 i i i小的元素。例如,最小值是第1个顺序统计量( i = 1 i=1 i=1),最大值是第 n n n个统计量。中位数比较特殊,当 n n n为奇数时,它是唯一的;当 n n n为偶数时,存在两个中位数,分别位于 i = n / 2 i=n/2 i=n/2 i = n / 2 + 1 i=n/2+1 i=n/2+1处,分别称作下中位数和上中位数。为了简便起见,本书中所用的“中位数”都是指下中位数。

本章将讨论从一个由 n n n个互异的元素构成的集合中选择第 i i i个顺序统计量的问题。虽然假设集合中的元素是互异的,但实际上书中的算法也适用于集合中包含重复元素的情形。问题定义如下:

  • 输入: 一个包含 n n n(互异的)数的集合 A A A和一个正数 i i i 1 ⩽ i ⩽ n 1\leqslant i \leqslant n 1in
  • 输出:元素 x ∈ A x \in A xA,且 A A A中恰好有 i − 1 i-1 i1个其他元素小于它。

利用堆排序或归并排序,我们可以在 O ( n l g n ) O(nlgn) O(nlgn)时间内解决这个问题。本章将介绍一个更快的算法,它可以在 O ( n ) O(n) O(n)时间内完成。

9.1 最小值和最大值

下面实现了书中的伪代码和练习题,包括:

  • P119:MINIMUM,找到最小值,见minimum
  • P119:同时找到最小值和最大值,见minmax
  • P120:练习9.1-1,找到第2小的元素(可以同时找到最小元素),见minimum2
def minimum(A):
    '''len(A) >= 1'''
    min1 = A[0]
    for i in range(1, len(A)):
        if min1 > A[i]:
            min1 = A[i]
            
    return min1


def minmax(A):
    '''len(A) >= 2'''
    if len(A) % 2 == 0:
        min1 = A[0]
        max1 = A[1]
        begin = 2
    else:
        min1 = max1 = A[0]
        begin = 1
        
    for i in range(begin, len(A), 2):
        if A[i] < A[i+1]:
            t_min = A[i]
            t_max = A[i+1]
        else:
            t_min = A[i+1]
            t_max = A[i]
        if t_min < min1:
            min1 = t_min
        if t_max > max1:
            max1 = t_max
    
    return min1, max1 


def minimum2(A):
    '''len(A) >= 2'''
    min1 = A[0]
    min2 = A[1]
    if min2 < min1:
        min1, min2 = min2, min1
    
    for i in range(2, len(A)):
        if A[i] < min1:
            min2 = min1
            min1 =A[i]
        elif A[i] < min2:
            min2 = A[i]
    
    return min1, min2     
    

def test():
    A = [3, 1, 4, 8, 9, 10, 2]
    print(minimum(A))
    print(minmax(A))
    print(minimum2(A))
    

if __name__ == '__main__':
    test()
9.2 期望为线性时间的选择算法

一般的选择问题看起来要比找最小值这样的问题更难,但令人惊奇的是,这两个问题的渐近运行时间确是相同的: Θ ( n ) \Theta(n) Θ(n)。本节介绍一种解决选择问题的分治算法RANDOMIZED-SELECT,它以第7章的快速排序算法为模型。与快速排序一样,我们仍然将数组进行递归划分。但与快速排序不同的是,快速排序会递归处理划分的两边,而RANDOMIZED-SELECT只处理划分的一边。

快速排序的期望运行时间是 Θ ( n l g n ) \Theta(nlgn) Θ(nlgn),最坏运行时间是 Θ ( n 2 ) \Theta(n^2) Θ(n2);该选择算法的期望运行时间为 Θ ( n ) \Theta(n) Θ(n),最坏运行时间是 Θ ( n 2 ) \Theta(n^2) Θ(n2)

下面实现了书中的伪代码和练习题,包括:

  • P120:RANDOMIZED-SELECT,见randomized_select
  • P123:练习9.2-3,给出RANDOMIZED-SELECT的一个基于循环的版本,见randomized_select_loop
def partition(A, p, r):
    x = A[r]
    i = p - 1
    for j in range(p, r):
        if A[j] <= x:
            i += 1
            A[i], A[j] = A[j], A[i]
    A[i+1], A[r] = A[r], A[i+1]
    return i + 1


def randomized_partition(A, p, r):
    import random
    i = random.randint(p, r)
    A[i], A[r] = A[r], A[i]
    return partition(A, p, r)


def randomized_select(A, p, r, i):
    if p == r: # i shoule be 1
        return A[p]
    q = randomized_partition(A, p, r)
    k = q - p + 1
    if i == k:
        return A[q]
    elif i < k:
        return randomized_select(A, p, q-1, i)
    else:
        return randomized_select(A, q+1, r, i-k)
    
    
def randomized_select_loop(A, i):
    begin, end = 0, len(A)-1
    while True:
        q = randomized_partition(A, begin, end)
        k = q - begin + 1
        if i == k:
            return A[q]
        elif i < k:
            end = q - 1
        else:
            begin, i = q + 1, i - k
    
     
def test():
    A = [3, 1, 4, 8, 9, 10, 2, 7, 5, 6]
    for i in range(1, len(A)+1):
        print(randomized_select(A, 0, len(A)-1, i), end=' ')
    
    print()
    for i in range(1, len(A)+1):
       print(randomized_select_loop(A, i), end=' ')
       
    
if __name__ == '__main__':
    test()
9.3 最坏情况为线性时间的选择算法

本节介绍了一个最坏情况下运行时间为 O ( n ) O(n) O(n)的选择算法SELECT。像RANDOMIZED-SELECT一样,该算法通过对数组的递归划分来找出所需元素,但是,在该算法中能够保证得到对数组的一个好的划分。该算法对来自快速排序的确定性划分算法PARTITION进行了修改,把划分的主元也作为输入参数。

算法还没看懂,代码还未完成…

上完了课,大概明白一点,然后花了三个多小时才实现…

def partition_for_select(A, p, r, pivot):
    for i in range(p, r+1):
        if A[i] == pivot: # 找到主元并与最后一个元素进行交换
            A[i], A[r] = A[r], A[i]
            break
        
    i = p - 1       
    for j in range(p, r):
        if A[j] <= pivot:
            i += 1
            A[i], A[j] = A[j], A[i]
    A[i+1], A[r] = A[r], A[i+1]
    
    return i + 1


def select(A, p, r, i):
    length = r - p + 1
    if  length <= 5: # 递归基本情况:返回第i小的数或返回中位数的中位数
        return sorted(A[p:r+1])[i-1]
    
    group = []
    for j in range(p, r+1, 5):
        if j + 5 <= r:
            temp = sorted(A[j:j+5]) # 代表插入排序
        else:
            temp = sorted(A[j:r+1])              
        group.append(temp)
   
    medians = [g[get_median_idx(len(g))] for g in group]
    m_medians = select(medians, 0, len(medians)-1, get_median_idx(len(medians))+1) # 递归寻找中位数的中位数
    q = partition_for_select(A, p, r, m_medians) # 中位数的中位数一定在A[p...r]
    k = q - p + 1
    if i == k:
        return A[q]
    elif i < k:
        return select(A, p, q-1, i)
    else:
        return select(A, q+1, r, i-k)


def test():
    B = list(range(1, 21))
    for i in range(1, len(B)+1):
        print(select(B, 0, len(B)-1, i), end=' ')
       
    
if __name__ == '__main__':
    test()
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值