当前位置: 首页 > news >正文

七大排序之快速排序

文章目录

  • 快速排序
    • 快速排序的思想
    • 基准值的选择
    • 代码
  • 二路快排
  • 三路快排
  • 总结


快速排序

快速排序的思想

  1. 从待排序区间选择一个数,作为基准值(pivot);
  2. Partition:遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
  3. 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 ==1,代表已经有序,或者小区间的长度 ==0,代表没有数据。

快速排序时间复杂度为O(nlogn),是一个不稳定的排序算法

图解如下:

在这里插入图片描述

基准值的选择

  1. 选择边上(左或者右):在接近有序的数组上,快排会退化为O(n^2),左右两个子区间严重不平衡,二叉树退化为链表。
  2. 随机选择:随机在当前数组中选取一个树作为基准值。
  3. 几树取中,一般都是三数取中。

第二种和第三种都能避免在递归过程中,在接近有序的数组上,快速排序的分区严重不平衡的问题。

代码

public static void quickSort(int[] arr){
        quickSortInternal(arr,0,arr.length-1);
    }
    private static void quickSortInternal(int[] arr, int l, int r) {
        //优化1:小区间上使用插入排序,减少递归的次数
        if (r-l<=15){
            insertionSort(arr, l, r);
            return;
        }
        //先获取分区点,分区点左侧全是小于该元素的区间,分区点右侧全是大于该元素的区间
        int p=partition(arr,l,r);
        quickSortInternal(arr, l, p-1);
        quickSortInternal(arr, p+1, r);
    }
    private static int partition(int[] arr, int l, int r) {
        //优化2:随机在当前数组中选一个数
        //能避免递归过程中,在接近有序的数组上,快速排序的分区严重不平衡的问题
        int randomIndex=random.nextInt(l,r);
        swap(arr,l,randomIndex);
        int v=arr[l];

        //1.先选一个基准点
        //int v=arr[l];

        //分区点:
        //arr[l+1...j] < v
        //arr[j+1...i) >= v
        //i表示当前正在扫描的元素
        int j=l;
        for (int i = l+1; i <=r ; i++) {
            //如果arr[i]小于v,就将大于v的第一个元素和正在扫描的元素进行交换
            if (arr[i]<v){
                swap(arr,i,j+1);
                j++;
            }
        }
        //将基准值和最后一个小于v的元素进行交换,基准值就到了最终位置
        swap(arr,j,l);
        return j;
    }

二路快排

为什么引入二路快排?

当待排序的集合中出现大量重复元素时,就会导致某个分区的元素过多(相等的元素全在一个区间中),造成递归过程中,递归树严重不平衡,快排退化为O(n^2)。二路快排就是将相等的元素均分到左右两个子区间,解决了递归树严重不平衡的问题。

图解如下:

在这里插入图片描述
代码如下:

private static final ThreadLocalRandom random=ThreadLocalRandom.current();
    public static void quickSort(int[] arr){
        quickSortInternal(arr,0,arr.length-1);
    }
    private static void quickSortInternal(int[] arr, int l, int r) {
        //优化1:小区间上使用插入排序,减少递归次数
        if (r-l<=15){
            insertionSort(arr,l,r);
            return;
        }
        //获取分区点
        int p=partition(arr,l,r);
        quickSortInternal(arr,l,p-1);
        quickSortInternal(arr,p+1,r);
    }
    private static int partition(int[] arr,int l,int r){
        //优化2:随机选取基准值
        int randomIndex=random.nextInt(l,r);
        swap(arr,l,randomIndex);
        int v=arr[l];
        int i=l+1;  //[l+1..i)<=v
        int j=r;    //(j..r]>=v
        while (i<=j){
            //i从前向后扫描,碰到第一个大于等于v的元素停止
            while (i<=j && arr[i]<v){
                i++;
            }
            //j从后向前扫描,碰到第一个小于等于v的元素停止
            while (i<=j && arr[j]>v){
                j--;
            }
            if (i>=j){
                break;
            }
            //如果此时i<j,交换i和j的元素
            swap(arr,i,j);
            i++;
            j--;
        }
        //此时j指向小于等于v的区间的最后一个元素
        swap(arr,l,j);
        return j;
    }
    private static void insertionSort(int[] arr,int l,int r){
        for (int i = l+1; i <=r ; i++) {
            for (int j = i; j >0 ; j--) {
                if (arr[j]>=arr[j-1]){
                    break;
                }else{
                    swap(arr,j,j-1);
                }
            }
        }
    }
    private static void swap(int[] arr,int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }

三路快排

三路快排思想:在一次分区函数的操作中,将所有相等的元素都放在最终位置,只需要在小于v和大于v的子区间上进行快排,所有相等的元素就不再处理了。

图解如下:

三路快排
代码如下:

private static final ThreadLocalRandom random=ThreadLocalRandom.current();
    public static void quickSort(int[] arr){
        quickSortInternal(arr,0,arr.length-1);
    }
    private static void quickSortInternal(int[] arr, int l, int r) {
        //小区间使用插入排序
        if (r-l<=15){
            insertionSort(arr,l,r);
            return;
        }
        //随机选取基准值
        int randomIndex=random.nextInt(l,r);
        swap(arr,l,randomIndex);
        int v=arr[l];
        int lt=l;  //arr[l+1..lt]<v   lt代表小于v的最后一个元素
        int i=lt+1;  //arr[lt+1..i)==v  i代表正在扫描的元素
        int gt=r+1;  //arr[gt..r]>v     gt指向第一个大于v的元素
        //i表示当前扫描元素
        while (i<gt){
            if (arr[i]<v){
                swap(arr,lt+1,i);
                lt++;
                i++;
            }else if (arr[i]==v){
                i++;
            }else{
                //当前扫描元素大于v时,将gt-1与i交换,i不加加,因为换过来的gt-1还没处理
                swap(arr,i,gt-1);
                gt--;
            }
        }
        swap(arr,l,lt);
        quickSortInternal(arr, l, lt-1);
        quickSortInternal(arr, gt, r);
    }
    public static void insertionSort(int[] arr,int l,int r){
        for (int i = l+1; i <=r ; i++) {
            for (int j = i; j >0 ; j--) {
                if (arr[j]>=arr[j-1]){
                    break;
                }else{
                    swap(arr,j,j-1);
                }
            }
        }
    }
    public static void swap(int[] arr,int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }

总结

  • 当待排序的集合重复元素并不多时,随机化快排已经可以解决问题,甚至性能比二路快排和三路快排还要好;
  • 当待排序集合中有大量重复元素时,使用二路快排或三路快排优化重复元素的处理;
  • 当待排序集合是一个接近有序的集合,分区点的选择就不能单纯选择最左侧或最右侧,可以使用随机化选择或三数取中。

相关文章:

  • vi vim 笔记心得2209010344
  • 忘记电脑密码的解决方法——使用pe工具重置电脑密码
  • 如何避免死锁呢?
  • Fedora36启用root,并且root直接通过ssh远程连接 2209010539
  • Slipped Conditions
  • 嵌套管程锁死
  • 图解LeetCode——1475. 商品折扣后的最终价格(难度:简单)
  • Java中的锁详解说明
  • GPIO相关介绍
  • 软件工程、软件生命周期、软件定义阶段、需求的层次/特征、概要设计、详细设计
  • 台式机电源更换笔记
  • 从文件资源管理器中隐藏文件
  • # Maven错误Error executing Maven
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (2020)Java后端开发----(面试题和笔试题)
  • 实现windows 窗体的自己画,网上摘抄的,学习了
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • 3.7、@ResponseBody 和 @RestController
  • Angular Elements 及其运作原理
  • Apache的80端口被占用以及访问时报错403
  • bootstrap创建登录注册页面
  • java取消线程实例
  • NSTimer学习笔记
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • webpack入门学习手记(二)
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 聊聊redis的数据结构的应用
  • 码农张的Bug人生 - 见面之礼
  • 前端临床手札——文件上传
  • 要让cordova项目适配iphoneX + ios11.4,总共要几步?三步
  • ​渐进式Web应用PWA的未来
  • ​马来语翻译中文去哪比较好?
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • #define,static,const,三种常量的区别
  • #Ubuntu(修改root信息)
  • (2)nginx 安装、启停
  • (Demo分享)利用原生JavaScript-随机数-实现做一个烟花案例
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (SpringBoot)第七章:SpringBoot日志文件
  • (黑马C++)L06 重载与继承
  • (全注解开发)学习Spring-MVC的第三天
  • (循环依赖问题)学习spring的第九天
  • (原創) 系統分析和系統設計有什麼差別? (OO)
  • (转)nsfocus-绿盟科技笔试题目
  • (转)Unity3DUnity3D在android下调试
  • .NET 使用 ILRepack 合并多个程序集(替代 ILMerge),避免引入额外的依赖
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • @ResponseBody
  • @开发者,一文搞懂什么是 C# 计时器!
  • [ linux ] linux 命令英文全称及解释
  • [ NOI 2001 ] 食物链
  • [ 隧道技术 ] 反弹shell的集中常见方式(二)bash反弹shell
  • [2017][note]基于空间交叉相位调制的两个连续波在few layer铋Bi中的全光switch——
  • [Asp.net mvc]国际化