递归与分治策略-2.9.2线性时间选择(取中位数的中位数基准)(第k小问题)

import java.util.Random;
/**
 * 线性时间选择——选择中位数的中位数基准
 */
public class test2_9_2 {
    static int n = 100;  
    static Comparable[] a = new Comparable[n];   //暂定长度为100的数组
    private static Comparable select(int left,int right,int k){
        int n = right - left+1;
        Comparable temp;
        if(n<76){   //right-left<75
            bubbleSort(left,right);
            return a[left+k-1];
        }
        //1.找出每组中位数并依次置换在最前排
        for(int i=0;i<=(n-5)/5;i++){
            int s = left+i*5;   //每组第一个元素
            int t = s + 4;      //每组最后一个元素
            for(int j=0;j<3;j++)
                bubble(s,t-j);  //遍历一次只做一次冒泡
            temp = a[s+2];
            a[s+2] = a[left+i];
            a[left+i] = temp;
        }
        //2.递归找出前(n-5)/5个元素的中位数X
        /**
         * 这样写方便大家理解,简化下写第三个参数是(n+5)/10;因为要在划分组数n1除以2,
         * 此数在计算机里int型整数会直接舍掉后面小数点,故需要进一位防止误差
         */
        //虽然返回的是Comparable类型的X元素,不是X元素下标,但执行select算法时,对位于最前排的中位数的中位数已经排好序
        Comparable x = select(left,left+(n-5)/5,(n-5)/5/2+1); 
        //3.根据之前两步确定的基准X利用快排思想进行划分,得出该基准对应下标,并排好左右大小
        3.1此步根据上一步得出的基准X遍历查询其下标,这样无需修改partition方法直接调用即可
        for(int q=left;q<right;q++)   
            if(a[q].compareTo(x)==0){
                temp = a[left];
                a[left] = a[q];
                a[q] = temp;
                break;
            }
        3.2得出X下标后开始划分,即使最坏情况下可以少处理1/4数组元素
        int i = partition(left,right);
        int j = i-left+1;
        if(k<=j) return select(left,i,k);
        else return select(i+1,right,k-j);
    }
    private static void bubble(int left,int right){  //冒泡排序,每次调用只起一次泡
        Comparable temp;
        for(int j=left;j<right;j++){  
            if(a[j+1].compareTo(a[j])<0){
                temp = a[j+1];
                a[j+1] = a[j];
                a[j] = temp;
            }
        }
    }
    private static void bubbleSort(int left,int right){  //冒泡排序n-1次冒泡
        Comparable temp;
        for(int i=left;i<right;i++){
            boolean YN = true;
            for(int j=left;j<right-i;j++){  
                if(a[j+1].compareTo(a[j])<0){
                    temp = a[j+1];
                    a[j+1] = a[j];
                    a[j] = temp;
                    YN = false;
                }
            }
            if(YN) break;
        }
    }
    private static int partition(int left,int right){
        int i = left,j = right+1;
        Comparable x = a[left],temp;
        while(true){
            while(a[++i].compareTo(x)<0&&i<right);
            while(a[--j].compareTo(x)>0);
            if(i>=j){
                a[left] = a[j];
                a[j] = x;
                break;
            }
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        return j;
    }
    public static void main(String[] args) {
        int k = 5;   //第k小元素
        Random random = new Random();
        System.out.print("排序前数组:");
        for(int i=0;i<n;i++){
            a[i] = random.nextInt(1000);   //产生n个0~1000的随机数并输入到数组里
            System.out.print(a[i]+" ");
        }
        System.out.println();

        System.out.println("第"+k+"小的数组元素是:"+select(0,a.length-1,k));

        System.out.print("排序后数组:");
        for(int i=0;i<n;i++){
            System.out.print(a[i]+" ");
        }

    }
}

运行结果如下:

排序前数组:453 979 588 279 608 721 677 27...(后面省略n个元素)
第5小的数组元素是:84
排序后数组:18 27 35 52 84 97 102 134 ...(后面省略n个元素)

为深入理解select算法的特点,在进行输入大量数据时能明显发现前排元素已成序,而后面的元素还是乱序。eg:

排序后数组:18 27 35 52 84 97 102 134.....
697 915 644 640 608 717 792

总结:select算法是防止在随机线性时间选择算法中出现最坏情况(如随机选择基准可能会选择到边缘元素)下而产生的。总体核心算法步骤如下:
①找出每组(长度为5)中位数并依次置换在最前排。
②递归找出最前排(n/5)个元素的中位数。
③利用快排思想递归确定该中位数的位置并做划分,左小右大。

线性时间选择

这样如果输入的元素数量级达到上百万上亿下,通过这样不断的划分,直到划分最后的长度小于一定数量级(如75)即可用简单排序算法(如冒泡)对前排元素进行完整排序,然后取出第k小元素。

感悟:费尽心思,取了一个又一个的中位数,只为考虑到存在最坏情况下,求一个更好的基准X。即便这样,每组遍历也只缩短了1/4的总长度。在追求时间复杂度最低的路上,你表达了你最深沉的爱意。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SL_World

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

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

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

打赏作者

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

抵扣说明:

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

余额充值