分治算法——线性时间寻找数组中第k小数

 采用最普通分治算法:O(N^2)

        Sub:使用一个支点将数组分为两部分;Relate:该支点所在数组的位置,若为k则返回,若大于k则将前半数组作为新的问题,反之将后半数组作为新的问题。Time:对于最好的情况,直接找到k;然而最坏的情况,每次支点都找到当前数组的第一个数,而k为最后一个数,这样的时间复杂度为O(N^2)

使用最普通的分治算法甚至不如直接对数组进行排序再寻找第k大的数

为了方便讲解,设N%5==0,若不为0可以通过在数组尾部补齐解决。  

采用改进过的分治算法:O(n)

        其最重要的改进就在于支点的选择,使其最差情况任为O(N)

        Sub:将数组根据支点分为三个新的数组(小于:array_lt、等于:array_eq、大于支点:array_gt),根据k的大小选择数组作为子问题。

        Relate:1.将数组拆分为n/5组每组5个,在每组5个中选出中值并以之分为两半(每组时间复杂度为c,有N/5组所以总时间复杂度为O(N)。2.将n/5个组的中值视为新数组,然后将其视为子问题1——在n/5个数中找出中间的数(T(2N/10));3.以第2步找出的中值的中值为支点,将数组分为小于、等于、大于支点的三部分O(N);4.分析知至少有3N/10个数小于等于支点数,同理至少有3N/10个数大于等于支点数

(见下图:先将每一组中小于中位数的数放于上部,大于的放于下部,再将中位数小于中位数的中位数的组放于中位数的中位数的左边,反之放到右边。左上角小于支点,右下角大于支点)

        取k所位于的部分作为新的数组,若k属于array_eq则直接返回;若属于array_lt则在其中找第k小的数;属于array_gt则在其中寻找第k-len(array_lt)-len(array_eq)小的数,作为子问题2。又由之前的分析知任何一个子问题的规模<=7N/10。

        Time:最坏情况下,T(n)=T(7n/10)+T(2n/10)+cn,通过画递归树再加以数归可得:T(n)=cn*(1+Σ(9/10)^i),所以时间复杂度为O(n)。

        代码如下:

from array import array
import numpy
A=[53,23,86,31,21,28,47,52,51,79,80,103,152,2,7,14,17,83,13,61,567,32,74,78,57]#,62,82,54,9,11
print('长度为:',len(A))

def my_sep(array,pivot):
    array_lt=[];array_gt=[];array_eq=[]
    array=array.flatten()
    for i in array:
        if i<pivot:
            array_lt.append(i)
        elif i>pivot:
            array_gt.append(i)
        else:
            array_eq.append(i)
    return array_lt,array_gt,array_eq

def my_f(a,k):
    N=len(a)
    
    #常数时间
    if N<=5:
        a.sort()
        return a[k]
    
    #不足5的倍数,在最后补上最大数,时间复杂度为O(N)
    if N%5!=0:
        ma_x=a[0]
        for i in range(N):
            if ma_x<a[i]:
                ma_x=a[i]
        for i in range(5-N%5):
            a.append(ma_x)
        N+=5-N%5
    

    #将数组分为5份,寻找支点
    b=numpy.array(a).reshape(N//5,5)
    c=[]
    for i in range(N//5):
        b[i].sort() #时间复杂度为O(C),所以整个时间复杂度为O(C*N//5)=O(C*N)=O(N)   
        c.append(b[i][2])
    _mid=my_f(c,(len(c)-1)//2)#找出中值的中值:T(N/5)
    
    print('支点为:',_mid)


    #此部分可以不用,只是为了展示数组的形状,如要展示建议将第22行改为N<=10
    '''
    p=0;q=N//5-1
    n=N//5
    while p<q:
            
        if b[p][2]>=_mid and b[q][2]<=_mid:
            b[[p,q],:]=b[[q,p],:]    
        else:
            if b[p][2]<_mid:p+=1
            if b[q][2]>_mid:q-=1
    print(b)        
    '''
    
    #将小于支点的数放到_lt,大于的放_gt,相等的放_eq。时间复杂度为O(N)
    array_lt,array_gt,array_eq=my_sep(b,_mid)
    if k<len(array_lt):
        return my_f(array_lt,k)
    elif k<len(array_lt)+len(array_eq):
        return array_eq[0]
    else:
        return my_f(array_gt,k-len(array_eq)-len(array_lt))


print(my_f(A,9))
A.sort()
print(A[9])

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值