线性时间选择算法(Java)

线性时间选择算法

最近算法课的知识点之一,自己以前没遇见过(是我菜了没错),这次写一篇。

那么什么是线性时间选择呢???一句话,在线性时间内完成选择

一般情况下是这样的,我们想要找出一个数组中的最大值或最小值,那就只需要一次排列,然后输出第一个或最后一个元素就行了,但如果是要找出一个数组中的第k小的元素呢?

  1. 在某些特殊情况下,很容易设计出解选择问题的线性时间算法。如:当要选择最大元素或最小元素时,显然可以在O(n)时间完成。(遍历一次)

  2. 一般的选择问题,特别是中位数的选择问题似乎比最小(大)元素要难。但实际上,从渐近阶的意义上,它们是一样的。也可以在O(n)时间完成。

之前我记得我更过一篇,里面说到就是先排个序,然后通过下标去得到,因此这个问题的解决效率取决于排序算法的效率。

但是现在不一样了,我们有了更好的办法。

随机划分线性选择

分析

随机划分线性选择。模仿快速排序算法,对数组在left——right范围内进行一次划分(partition方法,我们这里默认采用随机元素为基准),得到基准下标i,不大于基准的在左边,比基准大的在右边。计算出left——i(包含i)中有多少个元素(j),与k进行比较。

  1. 如果k<j,说明当前left——right中第k小的在左边序列中,那么从left——i-1继续上述步骤寻找第k小。
  2. 如果k=j,说明基准i就是left——right中第k小,返回a[j]。(因为前面的都不大于它,后面的都大于它,不管左边是序列怎么排的,整体第k(j)小就是他)
  3. 如果k>j,说明第k小在右边序列,大于基准a[i],那么从i+1——right继续划分,并将k改为k-j。(整体序列提出了前j个元素,那么第k小在剩下的序列中就是第k-j小了)

一直到left=right的时候就可以返回a[left]了(其实我觉得如果有上述2的情况的话就不需要这个了)

这是课堂上PPT给的思路

这是算法导论的(就是我刚刚说的)
在这里插入图片描述

代码

    /*
     * @Title randomizedSelect
     * @Description 基于随机元素为基准的线性时间选择
     * @author 滑技工厂
     * @Date 2020/3/26
     * @param [a, L, R, k -> L---R中的第k小]
     * @return int
     * @throws
     */
    public static int randomizedSelect(int[] a, int L, int R, int k) {
        if (L == R)
            return a[L];
        //获取为基准的随机元素的下标
        int i = randomizedPartition(a, L, R);
        //j为划分后左序列到基准(包含基准)的元素个数
        int j = i - L + 1;
        if (k < j)//如果k小于j,说明在基准i的左边
            return randomizedSelect(a, L, i - 1, k);
        else if (k == j)//
            return a[i];
        else//k大于j 说明在i的右边序列
            return randomizedSelect(a, i + 1, R, k - j);
    }

利用中位数线性时间选择

分析

算法的思路:如果能在线性时间内找到一个划分基准使得按这个基准所划分出的2个子数组的长度都至少为原数组长度的ε倍(0<ε<1),那么就可以在最坏情况下用O(n)时间完成选择任务。例如,当ε=9/10,算法递归调用所产生的子数组的长度至少缩短1/10。所以,在最坏情况下,算法所需的计算时间T(n)满足递推式T(n)<=T(9n/10)+O(n)。由此可得T(n)=O(n)。

先描述下过程。
将n个输入元素划分成n/5个组,每组5个元素,最只可能有一个组不是5个元素。用任意一种排序算法,将每组中元素排好序,并取出中位数,共n/5个。

递归调用Select来找出这n/5个元素中的中位数。如果n/5是个偶数,就找它两个中位数中较大的一个。以该元素作为划分基准。

这种情况下,找出的基准x至少比3(n-5)/10个元素大,同理也比3(n-5)/10个元素小。(下图中箭头指向是从大到小,红+蓝的数量为3(n-5)/10)而当n>=75时,3(n-5)/10>=n/4所以按此基准划分所得的两个子数组的长度都至少缩短1/4
在这里插入图片描述
得到划分基准后,后面就和第一个一样,计算左序列个数,和k进行比较。判断在左序列还是右序列,在递归调用该方法(k在右序列仍要减j),直到k=j为止,返回那个基准。

例题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码

    /*
     * @Title select
     * @Description 利用中位数线性时间选择
     * @author 滑技工厂
     * @Date 2020/3/27
     * @param [a, l, r, k]
     * @return int
     * @throws
     */
    public static int select(int[] a, int l, int r, int k) {
        if (r - l < 75) {
            insertSort(a, l, r);    //用插入排序进行排序
            return a[l + k - 1];
        }
        int group = (r - l + 5) / 5;
        for (int i = 0; i < group; i++) {
            int left = l + 5 * i;
            int right = (l + i * 5 + 4) > r ? r : l + i * 5 + 4;  //如果超出右边界就用右边界赋值
            int mid = (left + right) / 2;
            insertSort(a, left, right);
            swap(a, l + i, mid);     // 将各组中位数与前i个
        }
        int pivot = select(a, l, l + group - 1, (group + 1) / 2);  //找出中位数的中位数
        int p = partition(a, l, r, pivot);    //用中位数的中位数作为基准的位置
        int j = p - l + 1;       //leftNum用来记录基准位置的前边的元素个数
        if (k == j)
            return a[p];
        else if (k < j)
            return select(a, l, p - 1, k);
        else                    //若k在基准位子的后边,则要从基准位置的后边数起,即第(k - leftNum - 1)个
            return select(a, p + 1, r, k - j - 1);
    }

详细代码去我的G站

作业又完成了一个 (^-^)V

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章