• 线性时间选择:从n个元素中找出第k小的元素


    一、解决该问题的一般思路是:

    将数组a[p,r]分成两个字数组a[p,i]和a[i+1,r],使a[p,i]中的每一个元素都大于a[i+1,r]中的元素,接着算法计算字数组a[p,i]中的元素个数j,

    如果k<=j则a[p,r]中第k小的元素落在子数组a[p,i]中;

    如果k>j则第k小元素落在字数组a[i+1,r]中,只要找a[i+1,r]中第k-r小的元素;

    该算法成为RandomizedSelect方法,划分函数RandomizedPartition生成一个p和r之间的随机整数,RandomizedPartition产生的划分基准是随机的,可以证明,该算法可以在O(n)平均时间内找到n个元素中的第k小元素。但是最坏情况下,该算法需要Ω(n^2)计算时间。

    二、有一个比较好的方法可以在最坏O(n)时间完成该任务。

    算法思想如下:如果能够在线性时间内找到一个划分基准,使得按这个标准所划分出的两个子数组的长度都至少为元数组长度的ε倍,其中0<ε<1,那么在最坏O(n)时间内就可以完成该任务。

    例如:ε=9/10,算法递归调用所产生的子数组的长度至少缩短1/10,所以最坏情况下,算法所需要的计算时间T(n),满足递归式T(n)<=T(9n/10)+O(n)。由此可得,T(n)=O(n)。

    算法实现步骤如下:

    1、将n个输入元素划分成floor(n/5)个组,每组5个元素,除可能有一个组不是5个元素外。用任意一种排序算法将每组中的元素排好序,并取出每组的中位数,共floor(n/5)个。

    2、递归调用Select找到floor(n/5)个元素的中位数,如果floor(n/5)是偶数,就找到它的两个中位数中的较大的一个作为划分基准。

    只要等于基准的元素不太多,利用这个基准来划分的两个子数组的大小就不会相差太远,为了简化问题,假设数组中的元素互不相同。

    此时,找到的基准x至少比3*ceiling[(n-5)/10]个元素大,因为在每个组中有两个元素小于本组中的中位数,而floor(n/5)个中位数中又有floor[(n-5)/10]个小于基准x。

    (假设k=floor(n/5),则k个元素的中位数左边的元素个数为floor[(k-1)/2)],例如k=2r时,则floor[(2r-1)/2]=r,并且k=2r+1时,floor[(2r+1-1)/2]=r表示n个元素的中位数的中位数在位置r+1,它的左边有r个元素)

    设k=n/5中位数总数为floor(k)=floor((k-1)/2)+ceiling((k-1)/2)+1,其中的1表示x占据的位置,x左边有floor((k-1)/2)个元素,x右边有ceiling((k-1)/2)个元素(x为中位数的中位数)。

    因此比x大的元素个数为3*ceiling((k-1)/2)=3*ceiling((n-5)/10)个,因为每个中位数的中位数所在的组中有5个元素,其中有两个是大于该中位数的中位数的,因此总共是每组中至少有3个大于x的元素,总共有

    3*ceiling((n-5)/10)个。当n>=75时,3*ceiling((n-5)/10)>=n/4。

    所以按照此划分基准所得到的两个子数组的长度都至少缩短1/4。

    View Code
    Type Select(Type a[], int p, int r, int k)
    {
            if (r-p < 75) {
                    sort the array a[p,r] with any simple algorithm;
                    return a[p+k-1];
            }
            int n = r - p + 1;
            for (int i = 0; i <= (n-5)/5; i++) {
                    swap(a[p+i], data);//data is the third small element between a[p+5*i] and a[p+5*i+4];
            }
            Type x = Select(a, p, p+(n-5)/5, (n-5)/10);
            int i = Partition(a, p, r, x);
            j = i - p + 1;
            if (k <= j) {
                    return Select(a, p, i, k);
            }
            esle {
                    return Select(a, i+1, r, k-j);
            }
    }

    当n>=75时算法的递归调用部分才执行;

    当n<75时,select算法的计算时间不超过一个常数。

    算法以x为划分基准调用函数Partition对数组a[p,r]进行划分,这需要O(n)时间,for循环执行n/5次,每次需要O(1)时间,因此for循环需要O(1)时间。

    设对n个元素的数组调用Select需要T(n)时间,那么找中位数的中位数x至多需要T(n/5)时间,按照算法所选择的基准x进行划分所得到的两个子数组,分别至多需要3n/4个元素(至少有n/4个元素大于基准元素,则至多有3n/4个元素小于基准元素)。

    当n<75时,T(n)<=C1 (C1,C2为常数) 

    当n>=75时,T(n)<=C2*n+T(n/5)+T(3n/4)

    此递归式的解为T(n)=O(n)

    参考《计算机算法设计与分析》一书介绍

  • 相关阅读:
    二分查找
    【递归】N位全排列
    深度学习训练平台Polyaxon食谱 | Polyaxon使用笔记
    PAT A1149 Dangerous Goods Packaging [蜜汁模拟+STL]
    PAT A1124 Raffle for Weibo Followers [模拟+STL]
    PAT A1144 The Missing Number [模拟+STL]
    PAT A1120 Friend Numbers [模拟]
    PAT A1100 Mars Numbers [字符串处理+硬核模拟]
    PAT A1095 Cars on Campus [排序+硬核模拟]
    PAT A1113 Integer Set Partition [模拟]
  • 原文地址:https://www.cnblogs.com/taotao315/p/2962123.html
Copyright © 2020-2023  润新知