快速排序及其应用

快速排序被称为是20世纪10大算法之一,可见其重要性,不明觉厉。
学习快速排序不单单只是一种排序算法,更重要的是其中包含的思想,分治思想。
下面进入正题,本文将给出多种快速排序的方法,并给予评价。

快速排序的思想是分治,分而治之,和归并排序一样,我们将大问题分解同小规模的具有相同解法的小问题。
快速排序的重点在分,归并侧重“并”,所谓分,指的是找到一个位置,这个位置的数将序列划分成两部分,左边比它小,右边比它大,然后分别对左右两边进行同样的操作,因此关键就在于找到这个位置。
通用函数:

    public static int less(Comparable a,Comparable b){
        return a.compareTo(b);
    }
    public static void exch(Comparable[] a,int i,int j){
        Comparable t=a[i];
        a[i]=a[j];
        a[j]=t;
    }
public static void show(Comparable[] a){
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
    }
 /********-------------*********/
    //两路快排
    public static void partition2(Comparable[] a,int left,int right){
        Random r=new Random();
        int k=left+r.nextInt(right-left+1);
        exch(a,left,k);
        Comparable t=a[left];
        int less=left;
        // 小于区的位置为[left+1,less]   这个区间
        for(int i=left+1;i<=right;i++){
        //当前数比 t小,小于区扩大
            if(less(a[i],t)<0){
                exch(a,++less,i);
            }
        }
        exch(a,left,less);
    }
    public static void sort2(Comparable[] a,int left,int right){
        if(left>=right){
            return;
        }
        int[] index=partiton3(a,left,right);
        sort2(a,left,index[0]);
        sort2(a,index[1],right);
    }
    public static void sort2(Comparable[] a){
        if(a==null|| a.length<=1){
            return;
        }
        int left=0,right=a.length-1;
        sort2(a,left,right);
    }

其中区间[left+1,less]为小于区的区间,i是游标,遍历整个数组,初始时刻,less=left,因此区间内没有数,i初始为left+1,即第一个数,
如果这个数小于t,那么它属于小于区,小于区扩大,即++less,在与i进行交换
如果这个数不小于t,那么不处理,i++就好
当循环遍历完成后,小于区的最后一个位置是less指向的数,这是一个循环不变式,和我们定义的时候是一样的,此时将它与第一个元素进行交换(less指向的数是小于t的,第一个数为t,交换后less左边全部是小于t的数),返回less。

上述算法虽然可以,但是存在缺陷:
如果数组中存在大量的相同元素,比如
1,1,2,2,2,2,2,2,3,
进行切分的时候,不均衡,全部是相同的话,一次切分就只有一个元素,退化成N*N的复杂度排序。

对此进行改善

    public static int partition(Comparable[] a,int left,int right){
        Random r=new Random();
        int k=left+r.nextInt(right-left+1);
        exch(a,left,k);
        Comparable t=a[left];
        int i=left+1,j=right;
        while(i<=j){
        //从左往右,如果小于t一直扩,知道扩不动,遇到大于等于
            while(less(a[i],t)<0 && i<=right){
                i++;
            }
            //从右往左扩,如果大于一直扩,知道碰到小于等于
            while(less(a[j],t)>0 && j>=left+1){
                j--;
            }
            if(i>j){
                break;
            }
            //交换 大于等于 和小于等于
            exch(a,i,j);
        }
        //循环结束后,j碰到的是小于等于t的数,因此第一个数与它交换
        exch(a,left,j);
        return j;
    }

    public static void sort(Comparable[] a,int left,int right){
        if(left>=right){
            return;
        }
        int index=partition(a,left,right);
        sort(a,left,index-1);
        sort(a,index+1,right);
    }
 public static void sort(Comparable[] a){
        if(a==null|| a.length<=1){
            return;
        }
        int left=0,right=a.length-1;
        sort(a,left,right);
    }

<= >= 将相等的数随机分到两边,尽量保证两边切分不至于太过
两边都是交换大于等于 小于等于, 能够更加均衡一些,不至于退化到普通排序

三路排序:划分 小于区,等于区, 大于区

public static int[] partiton3(Comparable[] a,int left,int right){
        Random r=new Random();
        int k=left+r.nextInt(right-left+1);
        exch(a,left,k);
        Comparable t=a[left];
        int less=left,more=right,i=left+1;
        //循环不变条件  [left+1,less] less 初始化为left less左边包括less都是小于他的数
        //循环不变条件  (more,right] more 初始化为right,开区间
        while(i<=more){
            //  [ <] [====] i  i小于t 将i与小于区的下一个元素也就是等于区的第一个元素交换,小于区扩大
            if(less(a[i],t)<0){
                exch(a,i++,++less);
            }else if(less(a[i],t)==0){
                i++;
            }else{
                exch(a,more--,i);
            }
        }
        //less指向的是小于区的数,与第一个数交换
        exch(a,left,less);
        return new int[]{less-1,more+1};
    }

    public static void sort3(Comparable[] a,int left,int right){
        if(left>=right){
            return;
        }
        int[] index=partiton3(a,left,right);
        sort3(a,left,index[0]);
        sort3(a,index[1],right);
    }

    public static void sort3(Comparable[] a){
        if(a==null|| a.length<=1){
            return;
        }
        int left=0,right=a.length-1;
        sort3(a,left,right);
    }

应用:找第K大的数

    //找第k大的数
    public static Comparable findKthNum(Comparable[] a,int k) {
        Comparable t = findKthNum(a, 0, a.length - 1,k);
        return t;
    }
    public static Comparable findKthNum(Comparable[] a, int left, int right, int k) {
        if(left>right){
            return -1;
        }
        int len = a.length;
        int index = QuickAll.partition(a, left, right);
        if (k == len - index) {
            return a[index];
        } else if (k > len - index) {
            return findKthNum(a, left, index - 1, k);
        } else {
            return findKthNum(a, index + 1, right, k);
        }
    }

    public static void main(String[] args) {
        Integer[] a={3,1,5,4,7,2,6};
//        QuickAll.sort(a);
        System.out.println(findKthNum(a,8));
    }
}

partition后返回一个数,左边比小,右边比它大,返回index,说明这个数是当前数组中第len-index 大的一个数,如果k比这个数大,那么往前去找,如果比他小,往后去找。
复杂度分析:我们假设每次切分都是一半,均匀的
第一次partition的过程,时间复杂度是O(N)
第二次是O(n/2)
第三次是O(n/4)

O(1)

T(n)=n+n/2+n/4+…+1;
=2n
复杂度是O(N)

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页