排序总结—快速排序

一路快排

思想:
从数组中选取一个数作为基准值,按照从大到小排序小于基准值的放在基准值的左边,大于基准值的在基准值的右边
核心partition过程:
选取数组第一个元素作为基准值,小于区[left+1,j-1],大于等于区[j,i-1],让变量i从left+1位置开始遍历数组,j变量位置是小于区间的而最后一个数,若是i位置的元素是小于基准值的数,就让小于区间增大,即小于基准值的数与大于等于区的第一个数进行交换,小于区扩大,依次比较交换,直到遍历整个数组。最终返回基准值的位置,基准值左边为小于区间,右边为大于区间,递归即可完全排序

private static int partition1(int[] arr, int left, int right) {
        int value = arr[left];
        int i = left + 1;
        int j = left;
        //让i变量去遍历整个数组,j变量位置是小于区间的而最后一个数
        while (i <= right){
            //若i位置的元素是小于基准值的数 就让小于区间增大
            if (arr[i] < value){
                swap(arr,++j,i++);
            }else {//否则i向后遍历寻找小于基准值的数,找到后交换位置
                i++;
            }
        }
        //直到i遍历完序列中所有数后,将小于区最后一个数与基准值进行交换
        swap(arr,left,j);
        return j;
    }
 private static void swap(int[] arr, int i, int j) {
        if(i != j) {
            arr[i] ^= arr[j];
            arr[j] ^= arr[i];
            arr[i] ^= arr[j];
        } 
    }

存在问题:当数组中等于基准值的元素太多时,会将等于基准值的元素划分在大于区,这是两个区域的大小会严重失衡,会导致分区的次数大大增加,这时排序的时间复杂度就会退化,从O(nlogn)退化为O(n^2)

二路快排

优化:此时,我们优化为双路快排,双路快排的思想是,定义两个变量,一个从数组的左边开始进行遍历,一个从序列的右边开始进行遍历,把右边遍历时比基准值小的数与左边遍历比基准值大的数进行交换,依次比较交换,直到两个变量相等,这样的方法使得等于基准时的数尽可能平均的分摊在两个区域,解决失衡问题
核心partition过程:

private static int partition(int[] arr, int left, int right) {
        int val = arr[left];
        int i = left + 1;
        int j = right;
        while (true){
            //随机选择一个数作为基准数
//            int index = (int) (Math.random()*(right-left+1)+1);
//            swap(arr,left,index);
            while (i <= right && arr[i] < val) {
                i++;
            }
            while (j >= left+1 && arr[j] > val){
              j--;
            }
            if (i > j){
                break;
            }
            swap(arr,i++,j--);
        }
        swap(arr,left,j);
        return j;
    }

存在问题:如果这是一个有序或接近有序的序列,如果我们选择最后一个或第一个元素作为基准元素,那每次得到的两个分区是不均等的,我们需要进行大约n次分区操作才能完成整个快排操作,这是快排的时间复杂度就会退化到O(n^2)
**解决:**引入随机快排,在选取基准值时,随机选取一个元素,将这个随机的元素与数组首元素进行交换,这是每次选取到最大最小值的概率就会无限小,此时解决有序导致的时间复杂度退化问题

三路快排

思想:将数组按照基准值分为三部分:大于区、小于区、等于区,分区的过程中等于基准值的元素已经到了最终位置,在下一次的递归排序时只需要进行大于和小于区的处理,直到将序列排序成功,对于等于值较多的序列,这种方法效率很高
核心partation过程:
让变量从数组开始进行遍历,当遇见比基准值小的元素,将小于基准值的元素与等于区第一个元素进行交换,(算法不稳定)小于区扩大,当遇见比基准值大的元素,将大于基准值的元素与等于区最后一个元素进行交换,大于区扩大,一次排序后返回等于区的数组范围

private static int[] partition(int[] arr, int left, int right) {
        int less = left - 1;
        int more = right + 1;
        int l = left;
        int index = (int) (Math.random()*(right-left+1)+left);
        swap(arr,right,index);
        int val = arr[right];
        while (l < more){
            if (arr[l] < val){
                swap(arr,++less,l++);
            }else if (arr[l] > val){
                swap(arr,--more,l);//此时遍历数组的变量l不移动,因为不能确定交换过来的数的大小,所以要在原地再次比较一次
            }else {
                l++;
            }
        }
        //返等于区
        return new int[]{less+1,more-1};
    }

分析:
稳定性:不稳定
时间复杂度:最好:O(nlogn) 最坏:O(n^2)
恐慌间复杂度:递归过程最好 O(logn) 最坏O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值