【数据结构】七大排序之快速排序详解(挖坑法快排,非递归快排,二路快排,三路快排)

news2025/1/18 8:53:37

目录

1.快速排序核心思路

2.挖坑法快速排序(递归)

2.1步骤

 2.2代码(详细注释)

3.非递归快排(用栈实现快速排序)

3.1思路

3.2代码

 4.二路快排

4.1思路

4.2代码

5.三路快排

5.1思路

5.2代码


1.快速排序核心思路

快速排序算法通过多次比较交换来实现排序,其排序流程如下:

(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。

(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。

(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。

(4)重复上述过程。

补充:选取分区点时尽可能的随机,保证分区点两侧都有元素。

分区点的选择:

1.三数取中法:在无序数组的首,中,尾这三个数据中,选择一个排在中间的数据作为分区点。

2.随机数法:在无序数组中随机选取一个数作为分区点。

2.挖坑法快速排序(递归)

2.1步骤

    1.设定一个分区点(一般为序列的最左边元素,也可以是最右变的元素)此时最左边的是一个坑。这里选择最左边元素7作为分区点。


    2.开辟两个指针,分别指向序列的头结点和尾结点(选取的分区点在左边,则先从右边出发。反之,选取的分区点在右边,则先从左边出发)。


    3.从右指针出发依次遍历序列,如果找到一个值比所选的分区点要小,则将此指针所指的值放在坑里,左指针向前移。此时j遍历到1这个位置,1<7,将1填入i所指的坑里。


    4.后从左指针出发(选取的分区点在左边,则后从左边出发。反之,选取的分区点在右边,则后从右边出发),依次便利序列,如果找到一个值比所选的分区点要大,则将此指针所指的值放在坑里,右指针向前移。


  5.  依次循环步骤4,5,直到左指针和右指针重合时,我们把分区点放入这连个指针重合的位置。

 2.2代码(详细注释)

    //挖坑法快速排序
    public static void quickSortHole(int[] arr){
        quickSortInternal(arr,0,arr.length);
    }

    private static void quickSortInternal(int[] arr, int l, int r) {
        //小数组使用直接插入排序最快
        if(r-l<=64){
            insertionSort(arr,l,r);
            return;
        }
        //先找到第一次排序后分区点的索引
        int p=partitionByHole(arr,l,r);
        //将分区点左边元素进行快速排序
        quickSortInternal(arr,l,p-1);
        //将分区点右边元素进行快速排序
        quickSortInternal(arr,p+1,r);
    }

    private static int partitionByHole(int[] arr, int l, int r) {
        //这里分区点定位最左边的值
        int pivot=arr[l];
        //两个指针,分别指向序列的头节点和尾节点
        int i=l;
        int j=r;
        while(i<j){
            //先从后向前遍历
            while(i<j && arr[j]>pivot){
                j--;
            }
            //此时j指针停在第一个小于pivot的地方,将这个值放入i所指的坑里,那么j所指的地方就变为了一个坑
            swap(arr,i,j);
            //然后从前向后遍历
            while(i<j && arr[i]<pivot){
                i++;
            }
            //此时i指针停在第一个大于pivot的地方,将这个值放入j所指的坑里
            swap(arr,i,j);
        }
        //此时,i和j指向同一位置,回填分区点
        arr[i]=pivot;
        return i;
    }

    //直接插入排序,选取无序区间的第一个元素插入到有序区间的正确位置上
    private static void insertionSort(int[] arr, int l, int r) {
        //无序区间[i,r)
        //有序区间[l,i)
        //无序区间的每一个元素遍历一遍
        for(int i=l+1;i<r;i++){
            //将无序区间的第一个元素与有序区间的元素比较
            for(int j=i;j>l;j--){
                if(arr[j]<arr[j-1]){
                    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;
    }

3.非递归快排(用栈实现快速排序)

3.1思路

递归调用的本质就是栈,栈是一个先进后出的数据结构。

1.将区间的左右两边入栈。让right取栈顶元素(是我们后入的区间的右边),再删除栈顶的元素。同样,让left取先入但是后出的元素(区间的左边)。此时我们拿到了一个区间[left,right],我们对这个区间进行一次快速排序。

2.此时一个分区点到了正确的位置,我们继续对左边区间和右边区间的首尾入栈。

3.当栈为空时,说明排序完成。

3.2代码

    //非递归的快排(栈)
    public static void quickSortNonRecursion(int[] arr){
        Deque<Integer>stack=new ArrayDeque<>();
        stack.push(arr.length-1);
        stack.push(0);
        while(!stack.isEmpty()){
            int l=stack.pop();
            int r=stack.pop();
            if(l>=r){
                continue;
            }
            int p=partitionByHole(arr,l,r);
            //处理左半区间
            stack.push(p-1);
            stack.push(l);
            //处理右半区间
            stack.push(r);
            stack.push(p+1);
        }
    }
    private static int partitionByHole(int[] arr, int l, int r) {
        //这里分区点定位最左边的值
        int pivot=arr[l];
        //两个指针,分别指向序列的头节点和尾节点
        int i=l;
        int j=r;
        while(i<j){
            //先从后向前遍历
            while(i<j && arr[j]>pivot){
                j--;
            }
            //此时j指针停在第一个小于pivot的地方,将这个值放入i所指的坑里,那么j所指的地方就变为了一个坑
            swap(arr,i,j);
            //然后从前向后遍历
            while(i<j && arr[i]<pivot){
                i++;
            }
            //此时i指针停在第一个大于pivot的地方,将这个值放入j所指的坑里
            swap(arr,i,j);
        }
        //此时,i和j指向同一位置,回填分区点
        arr[i]=pivot;
        return i;
    }

    //直接插入排序,选取无序区间的第一个元素插入到有序区间的正确位置上
    private static void insertionSort(int[] arr, int l, int r) {
        //无序区间[i,r)
        //有序区间[l,i)
        //无序区间的每一个元素遍历一遍
        for(int i=l+1;i<r;i++){
            //将无序区间的第一个元素与有序区间的元素比较
            for(int j=i;j>l;j--){
                if(arr[j]<arr[j-1]){
                    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;
    }

 4.二路快排

对于上面我写的快排,将小于等于基准值的数据全都放到了左边,大于的放到右边了,那么这样就会出现问题。不管是当条件大于等于还是小于等于,当数组中重复元素非常多的时候,等于基准值的元素太多,那么数组就会分成极度不平衡的两个部分,因为等于基准值的一部分总是集中在数组的一边。

此时,使用二路快排就可以进行优化,阻止效率的降低。

4.1思路

1.正如其名,选取最左边为分区点,将整个区间分为两条路,一路是大于等于分区点的元素(橙色部分),一路是小于与分区点的元素(粉色部分)。分区点v的下标此时为l,i指向当前正在扫描的数组元素。区间[l+1,j]为小于分区点的元素区间,区间[j+1,i]为大于等于分区点元素的区间。

 2.当arr[i]>=v时,直接i++即可。

 3.当arr[i]<v时,交换arr[j+1]和arr[i]。然后j++。

 4.当扫描完数组之后,将分区点和j所指元素交换,回填分区点,数组为如下分区。

4.2代码

    //二路快排
    public static void quickSort2(int[] arr){
        quickSortInternal(arr,0,arr.length);
    }

    private static void quickSortInternal(int[] arr, int l, int r) {
        if(r-l<=64){
            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) {
        //最左边元素作为分区点
        int v=arr[l];
        //初始j指向l
        int j=l;
        //初始i指向分区点后面第一个元素
        for(int i=l+1;i<r;i++){
            if(arr[i]<v){
                swap(arr,i,j+1);
                j++;
            }
        }
        //回填分区点
        swap(arr,l,j);
        return j;
    }

5.三路快排

三路快排就是在快速排序的基础上进一步优化的,它将数据分为三个部分:大于分区点,小于分区点和等于分区点。

5.1思路

1.将数据分为三个部分,i指向当前数组正在处理的元素。

区间[l+1,lt]为小于分区点的元素区间

区间[lt+1,i]为等于分区点的元素区间

区间[gt,r]为大于分区点的元素区间

 2.当arr[i]==v时,i++即可。

3.当arr[i]>v时,交换arr[i]和arr[gt-1]

 4.当arr[i]<v时,交换arr[i]和arr[lt]。当i和gt重合时,整个数组交换完毕。

5.2代码

    //三路快排
    public static void quickSort3(int[] arr){
        quickSortInternal3(arr,0,arr.length-1);
    }

    private static void quickSortInternal3(int[] arr, int l, int r) {
        if(r-l<=64){
            insertionSort(arr,l,r);
            return;
        }
        int v=arr[l];
        int lt=l;
        int gt=r+1;
        int i=l+1;
        //终止条件为i和gt重合
        while(i<gt){
            if(arr[i]<v){
                swap(arr,lt+1,i);
                lt++;
                i++;
            }else if(arr[i]>v){
                swap(arr,gt-1,i);
                gt--;
            }else{
                i++;
            }
        }
        //回填分区点
        swap(arr,l,lt);
        quickSortInternal3(arr,l,lt-1);
        quickSortInternal3(arr,gt,r);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/443334.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大白话chatGPT及其原理之快速理解篇

大白话chatGPT及其原理之快速理解篇 从GPT名字理解chatGPTchatGPT三步曲 声明&#xff1a;本文为原创&#xff0c;未经同意请勿转载&#xff0c;感谢配合&#x1f604; chatGPT今年年初的时候是非常火爆的&#xff0c;现在也有很多相关的应用和插件。当然现在也有很多新的技术出…

老宋 带你五分钟搞懂vue

Vue 1.1 什么是框架 任何编程语言在最初的时候都是没有框架的&#xff0c;后来随着在实际开发过程中不断总结『经验』&#xff0c;积累『最佳实践』&#xff0c;慢慢的人们发现很多『特定场景』下的『特定问题』总是可以『套用固定解决方案』。于是有人把成熟的『固定解决方案…

袋鼠云春季生长大会圆满落幕,带来数实融合下的新产品、新方案、新实践

4月20日&#xff0c;以“数实融合&#xff0c;韧性生长”为主题的袋鼠云春季生长大会圆满落幕。 在春季生长大会中&#xff0c;袋鼠云带来了数实融合趋势下的最新行业沉淀、最佳实践经验和行业前瞻性的产品发布。从大数据基础软件“数栈”、到低代码数字孪生世界“易知微”&…

离散数学-考纲版-01-命题逻辑

文章目录 1. 命题逻辑的等值演算与推理演算参考1.1 命题1.2 常用联结词1.3 命题公式命题公式的分类-重言式-矛盾式-可满足式等价关系式-逻辑等价 logically equivalent 1.4 命题的等值演算与推理基本等价式逻辑蕴涵重言式 logically implication重言蕴涵推到归结法 1.5 命题公式…

log4j2日志简单使用

log4j2日志使用 1、log4j2介绍 Apache Log4j2是对Log4j的升级版&#xff0c; log4j2借鉴了logback的一些优秀的设计&#xff0c;并且修复了一些问题&#xff0c;因此带来了一些重大的提升&#xff0c;主要有&#xff1a; 1、异常处理&#xff1a;在logback中&#xff0c;Appe…

Makefile通用模板

工程目录 假如我们有以下目录结构&#xff1a; . ├── inc │ ├── add.h │ └── sub.h ├── main.c └── src├── add.c└── sub.c文件中的内容如下&#xff1a; //main.c #include <stdio.h> #include "add.h" #include "sub.h&q…

Mysql 学习(六)Mysql的数据目录

数据库中数据的存放 Mysql中 InnoDB 和 MyISAM 这样的存储引擎都是把数据存储到磁盘上的&#xff0c;而我们把这种存放到磁盘上的东西叫做文件系统&#xff0c;当我们想读取对应数据的时候&#xff0c;就会把数据从文件系统上加载&#xff0c;并且处理返回给我们&#xff0c;当…

每日学术速递4.19

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Visual Instruction Tuning 标题&#xff1a;可视化指令调优 作者&#xff1a;Haotian Liu, Chunyuan Li, Qingyang Wu, Yong Jae Lee 文章链接&#xff1a;https://arxiv.org/ab…

Midjourney:一步一步教你如何使用 AI 绘画 MJ

一步一步如何使用 Midjourney 教程&#xff1a;教学怎么用 MJ&#xff1f; 一、Midjourney&#xff08;MJ&#xff09;是什么&#xff1f; Midjourney是一款使用文字描述来生成高质量图像的AI绘画工具。这篇文章主要介绍了Midjourney及其用途&#xff0c;并针对Midjourney的使…

python 定时任务执行命令行

1.使用场景&#xff1a; 定时执行jmeter脚本&#xff0c;通过python定时器隔一段时间执行命令行命令。 2.库&#xff1a; os、datetime、threading &#xff08;1&#xff09;利用threading.Timer()定时器实现定时任务 Timer方法说明Timer(interval, function, argsNone, k…

如何利用python实现TURF分析?

1.TRUF分析简介 TURF分析(Total Unduplicated Reach and Frequency)是累计净到达率和频次分析的简称。最初被应用于媒介研究领域。典型应用场景是&#xff0c;在既定条件下&#xff0c;例如预算等资源限制或就当前实施的媒体组合投放计划&#xff0c;哪些渠道组合能让广告投放…

【三十天精通Vue 3】第十二天 Vue 3 过滤器详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 引言一、Vue 3 过滤器概述1.1 过滤器的简介1.2 过滤器的作用1.3 过…

WEB通用漏洞水平垂直越权详解业务逻辑访问控制脆弱验证

目录 一、知识点概述 <分类> <原理简述> 二、水平越权示例——检测数据比对弱 <越权演示> <如何防护> 三、垂直越权示例——权限操作无验证 <越权演示> <漏洞成因> 四、访问控制示例——代码未引用验证 <越权演示> 五、脆…

如何才能写出一个符合预期的正则?

如何才能写出一个符合预期的正则&#xff1f; 正则表达式入门示例讲解1、java里正则表达式replaceAll连续的字符正则测试题主问题讲解 2、开发者遇到金额的校验正则描述正则测试 3、java正则表达式匹配字符串正则描述正则测试 4、关于#正则表达式#的问题&#xff0c;如何解决&a…

0基础自学软件测试 用这个方法 99%的人都成功了

对于大多数0基础的小白而言&#xff0c;刚开始学软件测试&#xff0c;肯定会遇到各种各样的难题&#xff0c;有时候问题多了&#xff0c;扛不住了&#xff0c;导致最后无法坚持&#xff0c;或者学的很杂&#xff0c;学而不精。 那么有哪些比较有效的方法和技巧&#xff0c;可以…

系统分析师之数据库系统(七)

目录 一、数据库概念 1.1 数据库管理系统DBMS 1.2 数据库系统DBS 二、数据库设计 2.1 数据库设计过程 2.2 E-R模型 2.3 关系代数 2.4 规范化理论 2.4.1 价值与用途 2.4.2 函数依赖 2.4.3 键 2.4.4 范式 2.4.5 无损分解 三、并发控制 3.1 基本概念 3.2 问题示例…

SCA技术进阶系列(二):代码同源检测技术在供应链安全治理中的应用

一、直击痛点&#xff1a;为什么需要同源检测 随着“数字中国”建设的不断提速&#xff0c;企业在数字化转型的创新实践中不断加大对开源技术的应用&#xff0c;引入开源组件完成应用需求开发已经成为了大多数研发工程师开发软件代码的主要手段。随之而来的一个痛点问题是&…

开启数字化之旅:VR全景视频带你进入真实而神奇的世界

引言&#xff1a;随着科技的不断发展&#xff0c;虚拟现实技术正在成为越来越多人所追捧和体验的技术。而VR全景视频作为虚拟现实技术的一种重要应用&#xff0c;也得到了越来越多人的关注。那么&#xff0c;VR全景视频到底是什么&#xff1f;它的优势和特点是什么&#xff1f;…

OpenGL入门教程之 变化颜色的三角形

一、 知识点 &#xff08;1&#xff09;着色器 着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说&#xff0c;着色器只是一种把输入转化为输出的程序。着色器也是一种非常独立的程序&#xff0c;因为它们之间不能相互通…

153. 寻找旋转排序数组中的最小值

已知一个长度为 n 的数组&#xff0c;预先按照升序排列&#xff0c;经由 1 到 n 次 旋转 后&#xff0c;得到输入数组。例如&#xff0c;原数组 nums [0,1,2,4,5,6,7] 在变化后可能得到&#xff1a; 若旋转 4 次&#xff0c;则可以得到 [4,5,6,7,0,1,2] 若旋转 7 次&#xff0…