[算法分析笔记]顺序统计和中值

1. 问题定义

n n n个元素的数组 A A A,查找第 k k k小的元素,即将数组按从小到大排序,排在第 k k k位的元素。
注意,这里只是要找到这个元素,并不要求将数组排序。

首先,我们来看一下最朴素的求解问题的想法是什么?
第一步,数组 A A A按从小到大排序。
第二部,顺序找到 A [ k ] A[k] A[k]并返回。
这个算法的算法时间复杂度:
排序的时间复杂度 + 找到第 k 个元素的时间复杂度 排序的时间复杂度+ 找到第k个元素的时间复杂度 排序的时间复杂度+找到第k个元素的时间复杂度

排序算法最优的时间复杂度已知是 O ( n l g n ) O(nlgn) O(nlgn)
那么,顺序查找到第 k k k位的元素的时间复杂度是什么呢?
k = 1 k=1 k=1,为最优时间
k = n k=n k=n,为最差时间为
k = ⌈ n + 1 2 ⌉ 或者 ⌊ n + 1 2 ⌋ k=\lceil \frac{n+1}{2} \rceil 或者\lfloor \frac{n+1}{2} \rfloor k=2n+1或者2n+1,为平均时间。

这里补充一点,不要把 A A A想象成数组,因为数组是连续地址空间的,可以通过A[x]来直接获取在x位置的元素。而应该把 A A A想成链表结构,每个元素的地址都存储是不连续的,每个元素里有一个地址指针指向下一个元素,因此获得某一元素地址必须从 A A A的第一个元素开始逐个往下查找。

在算法代码举例中,为了简化代码凸显算法,因此我们还是使用的数组结构。

那么有没有更优的算法呢?

2. 随机选择法

老规矩还是先奉上python源码,再举个例子来解释这个算法是如何运作的。

2.1 python源码

import random

def Rand_Partition(A,p,q):
    x = random.randint(p,q-1)
        A_head = []
    A_tail = []
    for a in A[p:q]:
        if a < A[x]: 
            A_head.append(a)
        elif a > A[x]:
            A_tail.append(a)
    return A[:p]+A_head+[A[x]]+A_tail+A[q:], len(A[:p]+A_head)

def Rand_Select(A,p,q,i):
    if p==q:
        return A[p]
    A, r = Rand_Partition(A,p,q)
    k = r-p+1
    if i==k:
        return A[r]
    if i<k:
        return Rand_Select(A,p,r-1,i)
    else:
        return Rand_Select(A,r+1,q,i-k)

2.2 代码分析

这个算法的主函数是Rand_Select, 且它是一个递归调用的函数。
Rand_Select有三个输入参数, A是一个无序的列表,是你要查找对象的列表。p是查找范围的起始位置,q是终止位置,i是你要查找元素在列表中按从小到大的排序序号,即查找第i小的元素。

Rand_Partition的作用是生成一个在p和q之间的随机数r,然后将A序列中小于这个A[r]的元素放到这个元素前面,而大于这个元素的元素放到它后面,然后返回这个随机数r和重新排列的A。

我们通过一个例子来更形象的理解它的运作过程。
数列A的初始状态如下图所示,我们要找值排在第7位的那个元素。
第一次进入时初始参数是Rand_Select(A, 0, 8, 7),再第一次Rand_Partition时选中了A[4]=8, 并且将小于8的元素全部移到它的左侧,大于8的元素则移到它右侧,此时返回8在A列表中的排位是4,小于我们要寻找的排位7,因此我们只需在8右侧的元素中进行递归寻找。

在进入到第一层递归时,我们查找的列表中只剩下3个元素,此时Rand_Partition随机选择了13这个元素,我们依旧是将比他大的移到左边,因为13是A列表中最大的元素,这会让13移到表尾。此时查看13的排位是8,大于7,则继续在13的左侧剩余元素中查找。

第二次进入递归时,表中元素仅剩10和11两个。如果此时随机选择算法选中了10,但因为这两个元素依旧按大小顺序排好了,因此这一轮中序列顺序不会改变。但是因为10的排序是6,所以继续进入第四层递归,且p=q=6,第四层递归直接返回了A[6]的元素。
在这里插入图片描述
这里补充一点,如果在第三层递归时,如果随机选中的元素是11的话,因为11的排序是7,正是我们要找的元素,那就不会进入第四层递归,而是直接返回该元素了。

3. 算法时间复杂度分析

在进行严谨的数学证明之前,请允许我们先直观的来想象一下在最佳和最坏的情况下,这个算法的时间复杂度是什么样的。

  • 最佳情况,是我们随机选择的数字正好将序列划分成 1 10 \frac 1 {10} 101 9 10 \frac 9 {10} 109 ,此时算法的复杂度为:
    T ( n ) < = T ( 9 10 n ) + θ ( n ) = θ ( n ) T(n)<= T(\frac 9{10}n) + \theta(n)=\theta(n) T(n)<=T(109n)+θ(n)=θ(n)
  • 最坏情况,是这个随机选择的数字是最大或最小值,将序列划分成 0 0 0 n − 1 n-1 n1,此时算法复杂度为:
    T ( n ) = T ( n − 1 ) + θ ( n ) = θ ( n 2 ) T(n) = T(n-1)+\theta(n)=\theta(n^2) T(n)=T(n1)+θ(n)=θ(n2)
    -接下来让我们来研究一下一般情况,或者称期望值。
    1. T ( n ) T(n) T(n)是随机选择算法作用于 n n n个元素列表运行时间的变量,假设随机数每次产生都是独立且随机的。
    2. 定义一个随机指标量
      X k = { 1 如果产生的随机是是 k : n − k − 1 0 其他情况  X_k=\begin{cases} 1 &\text{如果产生的随机是是} k: n-k-1 \\ 0 &\text{其他情况 } \end{cases} Xk={10如果产生的随机是是k:nk1其他情况 
    3. Rand_Partition对于 T ( n ) T(n) T(n)的划分有 n n n种情况,可以表示为如下形式,而使用 X k X_k Xk指标变量可以把这么多种情况巧妙的转换成和的形式。
      T ( n ) = { T ( m a x { 0 , n − 1 } ) + θ ( n ) 如果0:n-1划分 T ( m a x { 1 , n − 2 } ) + θ ( n ) 如果1:n-2划分 . . . T ( m a x { n − 1 , 0 } ) + θ ( n ) 如果n-1:0划分 T(n)=\begin{cases} T(max \{0,n-1 \})+\theta(n) &\text{如果0:n-1划分} \\ T(max \{1,n-2 \})+\theta(n) &\text{如果1:n-2划分} \\ ...\\ T(max \{n-1,0 \})+\theta(n) &\text{如果n-1:0划分} \end{cases} T(n)= T(max{0,n1})+θ(n)T(max{1,n2})+θ(n)...T(max{n1,0})+θ(n)如果0:n-1划分如果1:n-2划分如果n-1:0划分
      = ∑ k = 0 n − 1 X k ( T ( m a x { k , n − k − 1 } ) + θ ( n ) ) =\sum^{n-1}_{k=0} X_k(T(max \{k,n-k-1 \})+\theta(n)) =k=0n1Xk(T(max{k,nk1})+θ(n))

有了这个式子我们可以来计算他的期望值:
E [ T ( n ) ] = E [ ∑ k = 0 n − 1 X k ( T ( m a x { k , n − k − 1 } ) + θ ( n ) ) ] E[T(n)]=E[\sum^{n-1}_{k=0} X_k(T(max \{k,n-k-1 \})+\theta(n))] E[T(n)]=E[k=0n1Xk(T(max{k,nk1})+θ(n))]
= ∑ k = 0 n − 1 E [ X k ( T ( m a x { k , n − k − 1 } ) + θ ( n ) ) ] =\sum^{n-1}_{k=0} E[X_k(T(max \{k,n-k-1 \})+\theta(n))] =k=0n1E[Xk(T(max{k,nk1})+θ(n))]
= ∑ k = 0 n − 1 E [ X k ] E [ ( T ( m a x { k , n − k − 1 } ) + θ ( n ) ) ] =\sum^{n-1}_{k=0} E[X_k]E[(T(max \{k,n-k-1 \})+\theta(n))] =k=0n1E[Xk]E[(T(max{k,nk1})+θ(n))]
= 1 n ∑ k = 0 n − 1 E [ ( T ( m a x { k , n − k − 1 } ) ] + 1 n ∑ k = 0 n − 1 θ ( n ) =\frac1n\sum^{n-1}_{k=0} E[(T(max \{k,n-k-1 \})]+\frac1n\sum^{n-1}_{k=0}\theta(n) =n1k=0n1E[(T(max{k,nk1})]+n1k=0n1θ(n)
< = 2 n ∑ k = ⌊ n 2 ⌋ n − 1 E [ T ( k ) ] + θ ( n ) <=\frac 2n \sum^{n-1}_{k=\lfloor \frac n2 \rfloor}E[T(k)]+\theta(n) <=n2k=2nn1E[T(k)]+θ(n)

下一步,我们使用替代法,假设在 c c c足够大的情况下 E ( T ( b ) ) < c . k E(T(b))<c.k E(T(b))<c.k,那么就有:
E ( T ( n ) ) < = 2 n ∑ k = ⌊ n 2 ⌋ n − 1 E [ T ( k ) ] + θ ( n ) E(T(n))<=\frac 2n \sum^{n-1}_{k=\lfloor \frac n2 \rfloor}E[T(k)]+\theta(n) E(T(n))<=n2k=2nn1E[T(k)]+θ(n)
< = 2 n ∑ k = ⌊ n 2 ⌋ n − 1 c . k + θ ( n ) <= \frac 2n \sum^{n-1}_{k=\lfloor \frac n2 \rfloor} c.k +\theta(n) <=n2k=2nn1c.k+θ(n)
= 2 c n ∑ k = ⌊ n 2 ⌋ n − 1 k + θ ( n ) =\frac {2c}n \sum^{n-1}_{k=\lfloor \frac n2 \rfloor} k +\theta(n) =n2ck=2nn1k+θ(n)
其中 ∑ k = ⌊ n 2 ⌋ n − 1 k < 3 8 n 2 \sum^{n-1}_{k=\lfloor \frac n2 \rfloor}k<\frac38n^2 k=2nn1k<83n2,用等差数列求和公式即可得到,所以:
< = 2 c n . 3 8 n 2 + θ ( n ) <=\frac{2c}n . \frac38n^2+\theta(n) <=n2c.83n2+θ(n)
= 3 4 c n + θ ( n ) =\frac34 cn +\theta(n) =43cn+θ(n)
= c n − ( 1 4 c n − θ ( n ) ) =cn-(\frac14cn-\theta(n)) =cn(41cnθ(n))
当c大于某一值时,我们可以另 1 4 c n − θ ( n ) \frac14cn-\theta(n) 41cnθ(n)非负,这样就证明了 E ( T ( n ) ) < c . k ,当 c 足够大时 E(T(n))<c.k,当c足够大时 E(T(n))<c.k,当c足够大时
结论, E ( T ( n ) ) E(T(n)) E(T(n))的期望值是 θ ( n ) \theta(n) θ(n),即线性的,非常不错的性能表现。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小白的逆袭日记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值