【数据结构与算法】十大经典排序算法-快速排序

🌟个人博客:www.hellocode.top
🏰Java知识导航:Java-Navigate
🔥CSDN:HelloCode.
🌞知乎:HelloCode
🌴掘金:HelloCode
⚡如有问题,欢迎指正,一起学习~~


快速排序(Quick Sort)是一种高效的排序算法,是对冒泡排序的优化。它采用分治法(Divide and Conquer)的思想,将待排序序列不断分割成较小的子序列,然后对每个子序列进行排序,最后合并得到有序的序列。快速排序在大多数情况下具有优异的性能,是许多常见排序算法中最快的之一。

基本思想

快速排序动画演示

这里的动画用大佬五分钟学算法的图,很清晰

  1. 选择一个基准元素(pivot)作为参考点。
  2. 将所有比基准元素小的元素移到基准元素的左边,将所有比基准元素大的元素移到基准元素的右边。此过程称为分区(partitioning)。
  3. 对基准元素左右两边的子序列递归地进行相同的快速排序,直到子序列的大小为1或0,表示子序列已经有序。

如上图所示,快速排序的基本思想就是选取一个基准数,将基准数小的都放在左边,大的都放在右边,也就是每次循环都会确定出基准数应该在的正确位置。

代码实现

对应在代码层面,需要使用到递归法来实现,对于快速排序来说,递归相对还是很容易想到的

/**
 * @author HelloCode
 * @blog https://www.hellocode.top
 * @date 2023年08月08日 21:14
 */
public class QuickSort {
    public static void quickSort(int[] arr) {
        // 特殊情况处理
        if (arr == null || arr.length == 0 || arr.length == 1) {
            return;
        }
        // 递归入口
        sort(arr, 0, arr.length - 1);
    }

    private static void sort(int[] arr, int left, int right) {
        // 递归出口
        if (left > right) {
            return;
        }
        // 初始化双指针
        int i = left, j = right;
        // 选取基准数
        int base = arr[left];
        while (i != j) {
            // (注意!!!!)从右边开始
            // 找比基准数小的
            while (i < j && arr[j] >= base) {
                j--;
            }
            // 从左边找比基准数大的
            while (i < j && arr[i] <= base) {
                i++;
            }
            // 交换i j
            if (i < j) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        // 基准数归位(此时i == j)
        arr[left] = arr[i];
        arr[i] = base;
        // 开始左右两边分别快排
        sort(arr, left, i - 1);
        sort(arr, i + 1, right);
    }
}

这里先移动j还是先移动i主要是需要看基准数选取的位置,如果选最左边的数,就需要先移动j(确保i == j 时对应的值是小于基准数的,再把基准数和该数交换,符合排序规则)
如果选取的基准数是最右边,则先移动i指针(确保 i == j 时对应的值是大于基准数的)

测试:

/**
 * @author HelloCode
 * @blog https://www.hellocode.top
 * @date 2023年08月08日 21:14
 */
public class Test {
    public static void main(String[] args) {
        int[] arr = {21, 13, 4, 10, 7, 65, 32, 15, 32, 19, 33, 65, 89, 72, 12};
        System.out.println("排序前:" + Arrays.toString(arr));
        QuickSort.quickSort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
    }
}

image-20230808213942941

优化

快排的优化主要需要关注基准数的选取,如果待排序数组整体偏降序,此时还选最左侧的为基准数的话,效率就相对慢一些,所以选取一个好的基准数可以让快排的效率更加稳定~

主要的方法有以下几种:

  1. 随机选择基准元素:选择随机的基准元素可以降低最坏情况的概率,进而提高算法性能。
  2. 三数取中法:在选取基准元素时,不是简单地选取第一个或最后一个元素,而是选择中间元素、第一个元素和最后一个元素中的中位数作为基准元素,也可以降低最坏情况的概率。

这里基准数的选取可以很巧妙,还有很多种其他方法,都可以降低最坏情况的发生,本文以三数取中法为例:

/**
 * @author HelloCode
 * @blog https://www.hellocode.top
 * @date 2023年08月08日 21:14
 */
public class QuickSort {
    public static void quickSort(int[] arr) {
        // 特殊情况处理
        if (arr == null || arr.length == 0 || arr.length == 1) {
            return;
        }
        // 递归入口
        sort(arr, 0, arr.length - 1);
    }

    private static void sort(int[] arr, int left, int right) {
        // 递归出口
        if (left > right) {
            return;
        }
        // 初始化双指针
        int i = left, j = right;
        // 选取基准数
        getBase(arr, left, right);
        int base = arr[left];
        while (i != j) {
            // (注意!!!!)从右边开始
            // 找比基准数小的
            while (i < j && arr[j] >= base) {
                j--;
            }
            // 从左边找比基准数大的
            while (i < j && arr[i] <= base) {
                i++;
            }
            // 交换i j
            if (i < j) {
                swap(arr, i, j);
            }
        }
        // 基准数归位(此时i == j)
        arr[left] = arr[i];
        arr[i] = base;
        // 开始左右两边分别快排
        sort(arr, left, i - 1);
        sort(arr, i + 1, right);
    }

    // 取三点中间值(最终会移动到left位置,这样可以不改变原有代码)
    private static void getBase(int[] arr, int left, int right) {
        // 这里可以防止溢出且使用 >> 效率相对能高一些
        // 等价于 (left + right) / 2
        int mid = left + ((right - left) >> 1);
        // 确保第一个元素最小
        if (arr[left] > arr[right]) {
            swap(arr, left, right);
        }
        // 确保最后一个元素最大
        if (arr[mid] > arr[right]) {
            swap(arr, mid, right);
        }
        // 确定mid就是中间值,交换到最左边
        if (arr[left] < arr[mid]) {
            swap(arr, left, mid);
        }
        System.out.println("基准数为:" + arr[left]);
    }

    // 交换数组两下标元素位置
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

测试:

/**
 * @author HelloCode
 * @blog https://www.hellocode.top
 * @date 2023年08月07日 21:02
 */
public class Test {
    public static void main(String[] args) {
        int[] arr = {21, 13, 4, 10, 7, 65, 32, 15, 32, 19};
        System.out.println("排序前:" + Arrays.toString(arr));
        BubbleSort.bubbleSortOptimized1(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
    }
}
排序前:[21, 13, 4, 10, 7, 65, 32, 15, 32, 19, 33, 65, 89, 72, 12]
基准数为:15
基准数为:7
基准数为:4
基准数为:12
基准数为:10
基准数为:13
基准数为:32
基准数为:21
基准数为:19
基准数为:32
基准数为:65
基准数为:33
基准数为:65
基准数为:72
基准数为:89
排序后:[4, 7, 10, 12, 13, 15, 19, 21, 32, 32, 33, 65, 65, 72, 89]

总结

优点

  1. 高效性:快速排序是一种高效的排序算法,在大多数实际情况下,它的性能通常比其他常见排序算法(如冒泡排序、插入排序)更好。
  2. 原地排序:快速排序是原地排序算法,不需要额外的辅助空间,只需在原始数组上进行交换操作。

缺点

  1. 最坏情况下的性能:当待排序序列已经基本有序或完全逆序时,快速排序的时间复杂度退化为 O(n^2),导致性能下降。这是因为基准元素的选择可能导致分区不平衡,使得分区操作不再能有效地减少问题规模。
  2. 不稳定性:快速排序是一种不稳定的排序算法,意味着相等元素的相对顺序在排序后可能发生变化。这在某些情况下可能导致问题,特别是对于复杂对象的排序,需要额外的处理来保持稳定性。

复杂度

  • 时间复杂度:快速排序的平均时间复杂度为O(n log n),最坏情况下为O(n^2)。
  • 空间复杂度:快速排序是原地排序算法,空间复杂度为O(log n)。

适用于处理大规模数据集的排序,尤其在平均情况下,快速排序的性能较优。但在处理已经有序或近乎有序的数据集时,快速排序的性能可能会下降,这时候其他稳定的排序算法可能更合适。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI Chat 设计模式:11. 状态模式

本文是该系列的第十一篇&#xff0c;采用问答式的方式展开&#xff0c;问题由我提出&#xff0c;答案由 Chat AI 作出&#xff0c;灰色背景的文字则主要是我的一些思考和补充。 问题列表 Q.1 你知道状态模式吗A.1Q.2 它与有限状态机有什么联系吗&#xff1f;A.2Q.3 知道了&…

Redis—持久化

这里写目录标题 AOF三种写回策略写回策略的优缺点AOF 重写机制AOF后台重写AOF优缺点使用命令 RDBRDB 持久化的工作原理执行快照时&#xff0c;数据能被修改吗RDB 持久化的优点RDB 持久化的缺点 混合持久化大key对持久化的影响 AOF 保存写操作命令到日志的持久化方式&#xff0…

[LeetCode - Python] 11.乘最多水的容器(Medium);26. 删除有序数组中的重复项(Easy)

1.题目&#xff1a; 11.乘最多水的容器&#xff08;Medium&#xff09; 1.代码 1.普通双指针对撞 贪心算法 class Solution:def maxArea(self, height: List[int]) -> int:# 对撞双指针# 对比记录最大面积&#xff0c;并移动短板&#xff0c;重新计算&#xff1b;left,…

Netty:ChannelHandler的两个生命周期监听事件方法:handlerAdded 和 handlerRemoved

说明 io.netty.channel.ChannelHandler有两个生命周期监听事件方法&#xff1a; handlerAdded(ChannelHandlerContext ctx)&#xff1a;当ChannelHandler被添加到实际的上下文、并且已经准备就绪等待处理事件的时候被调用。 handlerRemoved(ChannelHandlerContext ctx)&#…

【云原生•监控】基于Prometheus实现自定义指标弹性伸缩(HPA)

【云原生•监控】基于Prometheus实现自定义指标弹性伸缩(HPA) 什么是弹性伸缩 「Autoscaling即弹性伸缩&#xff0c;是Kubernetes中的一种非常核心的功能&#xff0c;它可以根据给定的指标&#xff08;例如 CPU 或内存&#xff09;自动缩放Pod副本&#xff0c;从而可以更好地管…

Prometheus技术文档-概念

Prometheus是一个开源的项目连接如下&#xff1a; Prometheus首页、文档和下载 - 服务监控系统 - OSCHINA - 中文开源技术交流社区 基本概念&#xff1a; Prometheus是一个开源的系统监控和告警系统&#xff0c;由Google的BorgMon监控系统发展而来。它主要用于监控和度量各种…

带你认识红黑树

红黑树 一、什么是红黑树&#xff1f;1.1 AVL树1.2 红黑树 二、红黑树的特点三、红黑树的insert、delete3.1 insert3.1.1 父节点为空3.1.2 父节点为Black节点3.1.3 父节点为Red节点3.1.3.1 叔叔节点为Red节点3.1.3.2 叔叔节点为Black节点 3.2 delete3.2.1 删除节点有两个子节点…

Scratch 之 TurboWarp 常用插件介绍-1

今天带来2篇 TurboWarp 常用插件介绍。 什么你还没有 TurboWarp &#xff1f;快去下载一个吧 TurboWarp&#xff08;简称TW&#xff09; 在线版 | 离线版下载 TurboWarp优点 编译速度快于原版 Scratch 至少10倍拥有自定义帧的功能&#xff08;比如60 FPS&#xff09;造型编…

【博客691】VictoriaMetrics如何支持Multi Retention

VictoriaMetrics如何支持Multi Retention 场景&#xff1a; 实现Multi Retention Setup within VictoriaMetrics Cluster&#xff0c;使得为不同的监控数据采用不同的保存时间 Multi Retention实现方式 方式&#xff1a; VictoriaMetrics 的社区版本通过 -retentionPeriod 命…

【工具插件类教学】电脑端移动端缩放大图自适应Simple Zoom

目录 简介 1.创建Canvas并设置 2.使用预制体Zoom 3.商店地址 简介 特点: •易于使用和高度可定制。 •支持鼠标(桌面)和触摸(移动)。 •指定最小和最大缩放的限制。 •缩放指针(鼠标/手指)或屏幕上预定义的自定义位置。 •变焦时使用夹紧/弹性变焦类型。 •定义缩…

基于PHP的轻量级博客typecho

本文完成于 5 月中旬&#xff0c;发布时未在最新版本上验证&#xff1b; 什么是 typecho &#xff1f; Typecho 是一款基于 PHP 的博客软件&#xff0c;旨在成为世界上最强大的博客引擎。Typecho 在 GNU 通用公共许可证 2.0 下发布。支持多种数据库&#xff0c;原生支持 Markdo…

征稿 | 第三届粤港澳大湾区人工智能与大数据论坛(AIBDF 2023)

第三届粤港澳大湾区人工智能与大数据论坛&#xff08;AIBDF 2023&#xff09; 2023 3rd Guangdong-Hong Kong-Macao Greater Bay Area Artificial Intelligence And Big Data Forum 本次高端论坛围绕建设国家数字经济创新发展试验区进行选题。全面贯彻落实党的二十大精神&…

【C++进阶之路】继承与多态的概念考察

文章目录 一、问答题二、概念题三、答案与解析问答题概念题 一、问答题 什么是菱形继承&#xff1f;菱形继承的问题是什么&#xff1f;什么是菱形虚拟继承&#xff1f;如何解决数据冗余和二义性的。继承和组合的区别&#xff1f;什么时候用继承&#xff1f;什么时候用组合&…

linux基于信号量实现多线程生产者消费者模型

基于信号量实现多线程生产者消费者模型。 编程思路&#xff1a; 1.食物的初始化编号为100&#xff1a; beginnum 100&#xff1b; 2.仓库有5个空碗&#xff0c;最多保存5个食物&#xff1a;queue[5]&#xff1b; 3.初始化空碗的数量为5&#xff0c;食物的数量为0&#xff1a…

FFmpeg中AVIOContext的使用

通过FFmpeg对视频进行编解码时&#xff0c;如果输入文件存在本机或通过USB摄像头、笔记本内置摄像头获取数据时&#xff0c;可通过avformat_open_input接口中的第二个参数直接指定即可。但如果待处理的视频数据存在于内存块中时&#xff0c;该如何指定&#xff0c;可通过FFmpeg…

《孙子兵法》快速概览,有哪些章节?趣讲《孙子兵法》【第2讲】

《孙子兵法》快速概览&#xff0c;有哪些章节&#xff1f;趣讲《孙子兵法》【第2讲】 《孙子兵法》十一家注是一个有名的版本&#xff0c;十一家注是曹操、杜牧等十一人注释&#xff0c;曹操是真正的军事家&#xff0c;是名副其实的大咖。总共三卷十三篇&#xff0c;比较难记住…

3个月快速入门LoRa物联网传感器开发

在这里插入图片描述 快速入门LoRa物联网传感器开发 LoRa作为一种LPWAN(低功耗广域网络)无线通信技术,非常适合物联网传感器和行业应用。要快速掌握LoRa开发,需要系统学习理论知识,并通过实际项目积累经验。 摘要: 先学习LoRa基础知识:原理、网络架构、协议等,大概需要2周时间…

Java加密算法的应用与实现(MD5、SHA、DES、3DES、AES、RSA、ECC)

文章目录 一、散列加密算法1、概述2、常见算法&#xff08;MD5、SHA&#xff09;3、应用4、Java实现 二、对称加密算法1、概述2、常见算法&#xff08;DES、3DES、AES&#xff09;3、应用4、Java实现AES 三、非对称加密算法1、概述2、常见算法&#xff08;RSA、ElGamal、Rabin、…

【linux】ssh 和adb connect区别

问&#xff1a;ssh 与ping的区别 答&#xff1a;SSH&#xff08;Secure Shell&#xff09;和Ping是两种完全不同的网络工具。 SSH是一种加密的网络协议&#xff0c;用于安全地远程管理或访问远程计算机。它提供了一种安全的通信方式&#xff0c;可以在不安全的网络上进行远程登…

淘宝API接口为开发者提供了与淘宝平台进行数据交互和操作的便捷途径

淘宝API接口是指淘宝开放平台提供的一套接口&#xff0c;用于与淘宝网进行数据交互和操作。通过使用淘宝API接口&#xff0c;第三方开发者可以实现商品搜索、店铺信息获取、订单管理、商家服务等功能&#xff0c;从而实现与淘宝平台的对接和数据共享。 淘宝API接口的使用可以帮…