[算法导论][9][中位数和顺序统计量]

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

  • 顺序统计量

    • 在一个由n个元素组成的集合中,第i个顺序统计量(order statistic)是该集合中第i小的元素
    • 例如,在一个元素集合中,最小值是第1个顺序统计量,最大值是第n个顺序统计量(i=n)
  • 一个中位数是它所属集合的中点元素

9.1 最小值和最大值

9.1.1 在一个有n个元素的集合中,需要做多少次比较才能确定其最小元素呢
  • 例程: MINIMUM算法

  • 结论: 为了确定最小值,至少要做n-1次比较

9.1.2 同时找到最小值和最大值
  • 我们只需要最多 3 * Math.floor(n/2) 次比较就可以同时找到最小值和最大值

  • 具体方法如下

    1. 我们需要记录已知的最小值和最大值
    2. 我们将一对输入元素相互进行比较
    3. 我们将较小的与当前最小值比较,将较大的与当前最大值比较,这样,对每两个元素共需要3次比较
  • 如何设定已知的最小值和最大值的初始值依赖于n是奇数还是偶数

    • 如果n是奇数,我们就将最小值和最大值的初值都设为第一个元素的值,然后成对地处理余下地元素
    • 如果n是偶数,就对前两个元素做一次比较,以决定最小值和最大值的初值,然后与n是奇数的情形一样,成对处理剩下的元素
  • 代码实现: 同时求解最大值和最小值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    def minimum_and_maximum(arr):
        arr_len = len(arr)
        print(arr_len)
        if arr_len == 0:
            raise IndexError("param: arr is empty")
        if arr_len % 2:
            max_val, min_val = arr[0], arr[0]
            start = 1
        else:
            max_val, min_val = (arr[0], arr[1]) if (arr[0] > arr[1]) else (arr[1], arr[0])
            start = 2
    
        for i in range(start, arr_len, 2):
            temp_max, temp_min = (arr[i], arr[i + 1]) if (arr[i] > arr[i + 1]) else (arr[i + 1], arr[i])
            if temp_max > max_val:
                max_val = temp_max
            if temp_min < min_val:
                min_val = temp_min
    
        return max_val, min_val
    
    
    if __name__ == '__main__':
        arr = [-3, 19, -2, 4, 1, 3, 2, 16, 9, 10, 14, 8, 7, 17, 0, -1, 18]
        max_val, min_val = minimum_and_maximum(arr)
        print(max_val, min_val)
    

9.2 期望为线性时间的选择算法

  • 选择算法: 用于选择序列中的第i个顺序统计量

  • 伪代码

  • 解释

    1. 第一行检查递归的基本情况
    2. 若不符合基本情况,第三行调用RANDOMIZED-PARTITION将数组A[p...r]划分为两个子数组A[p..q-1]A[q+1..r],使得A[p..q-1]中的每个元素都小于或等于A[q],而A[q+1..r]中的元素都大于A[q]
    3. 然后检查A[q]是否是第i个统计量
    4. 若不是则继续递归的调用此算法
  • 代码实现: RANDOMIZED-SELECT算法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    import random
    
    
    def randomized_partition(arr, p, r):
        t = random.randint(p, r)
        arr[t], arr[r] = arr[r], arr[t]
        i = p - 1
        for j in range(p, r):
            if arr[j] < arr[r]:
                i += 1
                arr[i], arr[j] = arr[j], arr[i]
        arr[i + 1], arr[r] = arr[r], arr[i + 1]
        return i + 1
    
    
    def randomized_select(arr, p, r, i):
        if p == r:
            return arr[p]
        q = randomized_partition(arr, p, r)
        k = q - p + 1
        if k == i:
            return arr[q]
        elif k > i:
            return randomized_select(arr, p, q, i)
        else:
            return randomized_select(arr, q+1, r, i - k)
    
    
    if __name__ == '__main__':
        arr = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
        o = randomized_select(arr, 0, len(arr) - 1, 1)
        print(o)
    
  • 时间复杂度

    • 最坏情况
      • O(n2)
      • 每次划分都极不走运地总是按余下地元素中最大地来进行划分
    • 平均情况
      • O(n)
      • 又因为它是随机化的,所以不存在一个特定的会导致其最坏情况发生的输入数据

9.3 最坏情况为线性时间的选择算法

9.3.1 简介
  • SELECT算法通过对输入数组的递归划分来找出所需元素,但是,在该算法中能够保证得到对数组的一个好的划分
9.3.2 算法步骤
  1. 将输入数组的n个元素划分为floor(n / 5)组,每组有5个元素,且至多只有一组由剩下的n % 5个元素组成

  2. 寻找这ceil(n / 5)组中每一组的中位数: 首先对每组元素进行插入排序, 然后确定每组有序元素的中位数

  3. 对第二步中找到的ceil(n / 5)个中位数,递归调用SELECT以找出其中位数x

  4. 通过PARTITION函数按照x对输入数组进行划分, 假设x是数组的第k个顺序统计量

  5. 如果i=k,则返回x; 如果i<k,则在低区递归调用SELECT来找出第i小的元素;如果i>k,则在高区递归查找第i-k小的元素

9.3.3 算法实现:Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import math
import random


def divide_arr(arr, start, end):
    labels = [i for i in range(start, end, 5)]
    if end != start[-1]:
        labels.append(end)
    return labels


def find_median(arr, labels):
    for i in range(0, len(labels) - 1):
        insertion_sort(arr, labels[i], labels[i + 1])
    medians = []
    for i in range(0, len(labels) - 1):
        medians.append(arr[math.floor((labels[i] + labels[i + 1]) / 2)])
    return medians


def insertion_sort(arr, start, end):
    if start >= end + 1:
        return
    for i in range(start + 1, end):    
        key = arr[i]
        j = i - 1
        while j > start - 1 and arr[j] > key:
            arr[j + 1] = arr[j]
            j -= 1
        arr[j + 1] = key
        


def select(arr, start, end, i):
    if start == end - 1:
        return arr[start]
    elif start > end - 1:
        return arr[end - 1] 
    else:
        labels = divide_arr(arr, start, end)
        medians = find_median(arr, labels)
        x = select(medians, 0, len(medians), math.floor((len(medians)-1) / 2))
        o = partition(arr, start, end, x)
        if o + 1 - start == i:
            return arr[o]
        elif o + 1 - start < i:
            return select(arr, o + 1, end, i - (o + 1 - start))
        else:
            return select(arr, start, o, i) 


def partition(arr, p, r, x):
    i = p - 1
    n = i
    for j in range(p, r):
        if arr[j] <= x:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
            if arr[i] == x:
                n = i
    arr[i], arr[n] = arr[n], arr[i]
    return i


if __name__ == "__main__":
    for i in range(1, 11):
        print()
        arr = [8, 9, 3, 5, 4, 1, 7, 2, 0, 6]
        print(arr)
        o = select(arr, 0, len(arr), i)
        print(arr)
        print(o)
9.3.4 时间复杂度
  • 最坏情况下的递归式

  • 最坏情况下的时间复杂度

    • O(n), 带入主方法可以证明
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值