递归与分治策略-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;qif(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;jif(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;iboolean YN = true;
            for(int j=left;jif(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&&iwhile(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;i1000);   //产生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" ");
        }

    }
}

运行结果如下:

排序前数组: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)个元素的中位数。
③利用快排思想递归确定该中位数的位置并做划分,左小右大。

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

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

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

你可能感兴趣的:(递归与分治策略)