序列的元素选择问题

    本文探讨序列的元素选择问题。

一、最大值和最小值选择算法

    首先我们来看一下一个序列中最大值和最小值的选择问题,这是一个很常见的元素选择问题,也很简单。对于一个n个元素的集合,很明显至少要比较n-1次才能找出最大值或者最小值。我们使用Java来实现这个算法:

    public static int getMax(int[] arr){
        if(arr.length == 0)
            throw new IllegalArgumentException("数组为空");
        int max = arr[0];
        for(int i = 1 ; i < arr.length; i++){
            if(arr[i] > max)
                max = arr[i];
        }
        return max;
    }
    public static int getMin(int[] arr){
        if(arr.length == 0)
            throw new IllegalArgumentException("数组为空");
        int min = arr[0];
        for(int i = 1 ; i < arr.length; i++){
            if(arr[i] < min)
                min = arr[i];
        }
        return min;
    }
    这个算法十分简单,不做过多介绍。

    下面我们来思考一个问题,如何遍历一次序列便同时获得该序列的最大值和最小值。我们可能很快会萌生这样的想法:遍历数组,然后每个元素分别和之前的最大值和最小值做比较,然后更换最大值和最小值。但是这样的算法会导致循环内进行了四次比较,有没有优化方法?

    事实上我们可以这样做,使得循环内只需要做三次比较,而且减少了循环次数:

    我们先把最大值初始化为负无穷,把最小值初始化为正无穷,然后对序列中两个元素两个元素地进行比较,先比较出这两个元素的较大者,把较大者与之前的最大值比较,把较小者与之前的最小值比较。对于偶数个数的序列,这样两个两个能在循环完结束,但是当序列的元素个数是奇数的时候,我们就要单独比较最后一个元素。

    Java代码如下:

    public static int[] getMaxAndMin(int[] arr){
        if(arr.length == 0)
            throw new IllegalArgumentException("数组为空");
        int[] result = new int[2];
        if(arr.length == 1){
            result[0] = arr[0];
            result[1] = arr[0];
            return result;
        }
        result[0] = Integer.MAX_VALUE;
        result[1] = Integer.MIN_VALUE;
        int i = 1;     
        for(i = 1 ;i < arr.length; i+=2){
            //循环中的第一次比较 比较一对数字之间的大小
            if(arr[i]<arr[i-1]){
                //循环中的第二次比较 获取较小值
                if(result[0]>arr[i])
                    result[0] = arr[i];
                //循环中的第三次比较 获取较大值
                if(result[1]<arr[i-1])
                    result[1] = arr[i-1];
            }
            else{
                //循环中的第二次比较 获取较大值
                if(result[1]<arr[i])
                    result[1] = arr[i];
                //循环中的第三次比较 获取较小值
                if(result[0]>arr[i-1])
                    result[0] = arr[i-1];
            }
        }
        if(arr.length%2==1){
        //这时i等于arr.length 我们需要比较arr[arr.length-1]和最大值最小值
            if(arr[arr.length-1]<result[0]){
                result[0] = arr[arr.length-1];
            }
            if(arr[arr.length-1]>result[1])
                result[1] = arr[arr.length-1];
        }
        return result;
    }
    我们来分析一下比较次数,如果n是奇数,那么会比较(n-1)/2*3+1次,如果是偶数,会比较n/2*3次。


二、渐进时间复杂度为Θ(n)的选择算法

    我们从直观上看一般选择问题比最小值最大值选择问题会更复杂,但是实际上这两个问题的渐进运行时间是相同的,都是Θ(n)。我们下面将给出这种渐进时间复杂度为Θ(n)的一般选择算法。

    这实际上本质是分治法,对于寻找序列arr中索引在[p,r]的元素中第i小的元素,如果p和r相等,我们直接返回arr[p],否则我们用一个轴元素arr[0],把[p,r]分成[p,q-1]和[q+1,r],其中[p,q-1]中的元素都小于arr[0],[q+1]的元素都大于等于arr[0]。如果q恰好等于i,那么我们直接返回,否则我们继续划分区间,直到区间只有一个元素,直接返回。

    我们使用Java来实现这一算法:

    private static void swap(int[]a , int location1 , int location2){
        int temp;
        temp = a[location1];
        a[location1] = a[location2];
        a[location2] = temp;
    }
    private static int randomizedPartition(int[] a, int p, int r){
        int shaft = a[0];//第0个元素作为轴元素
        while(p!=r){
            while(p<r&&a[p]<shaft)
                p++;
            while(r>p&&a[r]>=shaft)
                r--;
            swap(a,p,r);
        }
        return p;
    }
    public static int randomizedSelect(int[] a, int p, int r ,int i){
        if(p == r)
        //检查递归的基本情况
            return a[p];
        int q = randomizedPartition(a, p, r);
        //将[p,r]划分为[p,q-1]和[q+1,r] 其中a[q]比左边的都大 比右边的都小
        int k = q - p;
        if(i == k)
            return a[q];
        else if(i < k)
            return randomizedSelect(a, p, q-1, i);
        else
            return randomizedSelect(a, q+1, r, i - k);
    }

    这个算法最坏情况的运行时间是Θ(n^2),在那样情况下每次划分都很不走运地将轴元素划分得很极端,但是它的平均情况下,我们可以在线性时间内找到任一顺序统计量。


三、最坏情况为O(n)的选择算法

    这种选择算法也是基于快速排序的划分算法,采用分治法的思想,最坏情况运行时间为O(n)。

    思路如下:

    1、将序列的n个元素划分为n/5向下取整组,每组5个元素,如果n不是5的倍数,那么其中一组有n mod 5个元素。

    2、寻找这n/5向上取整组中的每一组的中位数,使其存在于每一组的第二个元素,划分该组另外几个元素。

    3、找到每一组中位数的中位数,划分其他组。

    4、按照中位数的中位数对序列进行划分,让x成为第k小的元素。

    5、如果i=k,返回x。如果i<k,则在低区递归调用该算法,否则在高区递归调用该算法。


    这里这种算法我先不做实现了,先把思路列出来,我有一些疑问。在寻找中位数的时候显然不能将所有元素都使用插入排序排序好再来寻找,那样的时间复杂度太高。对于5个元素寻找中位数,我们可以通过6次比较寻得中位数(这里不写思路了,读者可以自己想一下),这个比较次数是可以接受的,但是对于寻找每一组元素的中位数的中位数,很显然这又是一个选择问题,那么我们该用什么样的算法来寻找中位数的中位数呢?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值