快速排序

1.一路快排:

  • 思路分析:在无序数组中选择一个基准值作为分区点,遍历数组之后,将小于该基准值的数放在左边,大于等于基准值的数放在右边。这样,将数组分为了小于区和大于等于区。根据分治思想,将两个区域分别进行排序,直到区间长度为1,说明此时数组有序。
  • 代码实现:
package www.first;

public class QuickSortTest{
    public static void quickSort(int[] arr){
        quickSortInternal(arr,0,arr.length-1);
    }
    public static void quickSortInternal(int[] arr,int l,int r){
        if(l>=r){
            return;
        }
        int p = patition(arr,l,r);
        quickSortInternal(arr,l,p-1);
        quickSortInternal(arr,p+1,r);
    }
    public static int patition(int[] arr,int l,int r){
        //默认选择第一个元素为基准值
        int value = arr[l];
        //小于区的位置
        int j = l;
        //从第二个元素开始比较
        int i = l+1;
        while(i<=r){
            if(arr[i]<value){
                //将大于区的第一个元素与当前元素进行交换
                //即:将小于区向后扩张
                swap(arr,++j,i++);
            }else{
                i++;
            }
        }
        //遍历结束后,即小于区元素已经全部找完,交换基准值到相应的位置
        //此时,[l,j-1]为小于区,[j+1,r]为大于等于区,可见j为分区点
        swap(arr,l,j);
        return j;
    }
    public static void swap(int[] arr,int i,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    public static void main(String[] args) {
        int[] data = new int[]{2,1,2,43,5,3,42,5,35};
        quickSort(data);
        for(int a:data){
            System.out.print(a+" ");
        }
    }
}

结果如下:
在这里插入图片描述

  • 一路快排的优化:每次取第一个元素为基准值,这样当数组近乎有序时,会导致我们的排序算法的时间复杂度退化为O(n^2)。此时,我们可以采取随机取基准值的方法来优化排序算法。优化部分代码如下:
public static int patition(int[] arr,int l,int r){
        //产生数组区间内的随机下标,并与第一个元素交换
        //此时的基准值是数组元素的随机元素
        int random = (int) (Math.random()*(l-r+1)+l);
        swap(arr,l,random);
        int value = arr[l];

2.二路快排:

  • 思路分析:一路快排在极端情况下使得我们的分区非常不平衡,会导致时间复杂度退化。优化的另一种方法就是双路快排,定义两个指针,一个从前往后遍历找到小于基准值的元素,一个从后往前遍历找到大于基准值的元素,最后将基准值放回等于区的起始位置。这样不仅大大提高我们的遍历速度,也会使我们的分区变得平衡。
  • 代码实现:
public static int patition(int[] arr,int l,int r){
        //选取数组一随机元素为基准值
        int random = (int) (Math.random()*(r-l+1)+l);
        swap(arr,l,random);
        int value = arr[l];
        //前指针,扫描并找出小于元素
        int i = l+1;
        //后指针,扫描并找出大于元素
        int j = r;
        while(true){
            //从前往后遍历,若小于基准值则扩大小于区
            //若大于基准值则停止
            while(i<=r&&arr[i]<value){
                i++;
            }
            //从后往前遍历,若大于基准值则扩大大于区
            //若小于基准值则停止
            while(j>=l+1&&arr[j]>value){
                j--;
            }
            //每次停止扫描之后进行判断,看是否扫描结束
            //未结束再交换,结束则跳出
            if(i>j){
                break;
            }
            //交换此时停止位置的数,然后继续扫描
            swap(arr,i++,j--);
        }
        //扫描结束,将基准值放到等于区的起始位置
        //此时j处于小于区的最后一个位置
        swap(arr,l,j);
        //交换之后,j是大于等于区的起始位置
        return j;
    }

3.三路快排:

  • 思路分析:前两种方式其实还是对相等的元素进行了比较交换,做了处理,若等于区元素很多,会耗费大量时间。三路快排就是为了解决这类问题,对等于区不做处理,选定基准值后,将数据分为三个区域:大于区、等于区、小于区。只对大于区和小于区进行分区排序处理,大大节省了时间。
  • 代码实现:
public static int[] patition(int[] arr,int l,int r){
        //选取随机元素作为基准值
        //并与最后一个元素进行交换
        int random = (int) (Math.random()*(r-l+1)+l);
        swap(arr,random,r);
        int value = arr[r];
        //定义小于区的起始位置
        int less = l-1;
        //定义大于区的起始位置
        int more = r+1;
        //l的最后位置是等于区的最后一个位置,即:大于区的前一个位置
        while(l<more){
            //若小于基准值,则less扩张,将此元素与等于区的第一个元素进行交换,l向后扫描
            if(arr[l]<value){
                swap(arr,++less,l++);
                //若大于基准值,则more扩张,将此元素与等于区的最后一个元素进行交换,
                //!!!注意:此时l不会向后扫描,而是会判断交换过来的元素
            }else if(arr[l]>value){
                swap(arr,--more,l);
            }else{
                //等于区不作处理
                l++;
            }
        }
        //返回等于区间
        return new int[]{less+1,more-1};
    }

public static void quickSortInternal(int[] arr,int l,int r){
        if(l>=r){
            return;
        }
        int[] p = patition(arr,l,r);
        //等于区间起始位置的前一个即是小于区的最后一个位置
        quickSortInternal(arr,l,p[0]-1);
        //等于区间末尾位置的后一个即使大于区的第一个位置
        quickSortInternal(arr,p[1]+1,r);
    }

4.快速排序总结:
1)时间复杂度:一般情况下,时间复杂度为O(nlogn)。但在极端情况下,例如:{1,2,3,3,4,5,6},选择第一个元素为基准值进行分区,造成大于等于区的元素太多,所以需要n次分区,才能完成快排,此时时间复杂度退化成了O(n^2)。
2)空间复杂度:快排每次递归都会返回一个分区点的位置,必须使用栈,所以空间复杂度就是栈使用的空间,分区次数一般情况下为logn,故快排的空间复杂度最好是O(logn),最坏是O(n)。
3)稳定性:确定分区点的过程中不断的进行交换,将小于的元素与大于区的第一个进行交换,可能会把相等的元素位置打乱,因此是一种不稳定的排序算法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值