Java玩转《啊哈算法》排序之快速排序

news2024/11/24 0:49:28
心无挂碍,无挂碍故,无有恐怖,远离颠倒梦想,究竟涅槃。

地图

  • 引子
  • 代码地址
  • 快速排序
    • 核心代码
    • 优劣
    • 完整代码
    • 演示
  • 课后习题

引子

搭嘎好!本人最近看的《啊哈算法》这本书写的确实不错,生动形象,在保持算法讲解准确性的同时又不失趣味性。

但对我来说,稍显遗憾的是,书籍代码是c语言,而不是本人常用的Java。

那就弥补遗憾,说干就干,把这本书的示例语言用java给翻译一遍!!!

于是就有了本篇博客,这是第三篇博客,主要讲解快速排序。
在这里插入图片描述

来不及买纸质书但又想尽快感受算法魅力的童鞋也甭担心,电子版的下载链接已经放到下方了,可尽情下载。

链接:https://pan.baidu.com/s/1imxiElcCorw2F-HJEnB-PA?pwd=jmgs
提取码:jmgs

代码地址

本文代码已开源:

git clone https://gitee.com/guqueyue/my-blog-demo.git

请切换到gitee分支,

然后查看aHaAlgorithm模块下的src/main/java/com/guqueyue/aHaAlgorithm/chapter_1_Sort即可!

快速排序

快速排序,名字听起来就很快!

那么,这么快的排序是怎么实现的呢?我们这里以升序为例(降序的话找数的逻辑相反),把它拆解成以下几步:

  1. 选定一个基准值,这里我们一般选取第一个数为基准值

  2. 设定双指针,也就是书里面说的哨兵。

    2.1 一个哨兵从右往左走找比 基准值小(大) 的数,一个哨兵从左往右找比 基准值大(小) 的数,找到了就交换两个数。

    2.2 等到两个哨兵相遇就交换基准值和哨兵

    这样数组的左边都是比 基准值小(大) 的数,右边都是比 基准值大(小) 的数。

  3. 左边界基准值位置-1 为一个数组,右边界基准值位置+1 为一个数组,再进行如上操作。

一直到左右边界相遇,则排序完成。

在这里插入图片描述

当然,这里面采用了递归的编程技巧,实现了分而治之的编程思想,才能达到我们想要的目的。

关于这个,我当初写的关于《算法图解》第三章<递归>、第四章<快速排序>里面也有相应的介绍:

肝了几万字,送给看了《算法图解》却是主攻Java的你和我(上篇)

相比于《算法图解》那本书,这本书讲快速排序明显要更加清晰直观。

下面是快速排序整体的流程图:
在这里插入图片描述

还没懂?列一个快速排序第一次交换的动图,是不是清晰很多了呢?

在这里插入图片描述
如果还是不是很明白,就去看书吧,里面有详细的图文步骤,我这里就不多加赘述啦。

核心代码

话已经说了很多了,直接上代码:

	/**
     * @Description 快速排序
     * @Param [arr: 数组, left: 左边界, right: 右边界]
     * @return void
     **/
    private static void quickSort(int[] arr, int left, int right) {

        if (left > right) { // 左边不能大于右边
            return;
        }

        int temp = arr[left]; // 基准值
        int i = left, j = right;
        while (i < j) {

            // 找到比基准值更小的数,右边先找(为啥一定要右边先找? - 1 3 2) 防止基准值是最小的数,左边先走,就会把基准值交换掉
            while (arr[j] >= temp && i < j) {
                j--;
            }

            // 找到比基准值更大的数,这边必须设置等于temp,不然左边的数不会走
            while (arr[i] <= temp && i < j) {
                i++;
            }

            // 如果没有相遇,则: i == j 时,没必要交换
            if (i < j) {
                int t = arr[i];
                arr[i] = arr[j];
                arr[j] = t;
            }
        }

        // 基准值归位
        arr[left] = arr[j];
        arr[j] = temp;

        // 这里 i,j 是相等的
        quickSort(arr, left, j-1); // 左半边继续排序
        quickSort(arr, j+1, right); // 右半边继续排序
    }

优劣

  • 空间复杂度为O(N),属于 原地排序算法 了,即不占用额外的空间。
  • 平均时间复杂度为O(NlogN), 但是有些情况下时间复杂度最高可达O(N^2),不过问题不大,最坏的情况也就和冒泡排序一样不是?

上面说到快速排序有最坏的情况时间复杂度为O(N^2),你能想到是哪种情况吗?

想不到的话也没关系,下文有提示。

完整代码

诺:

package com.guqueyue.aHaAlgorithm.chapter_1_Sort;

import java.util.Arrays;
import java.util.Scanner;

/**
 * @Author: guqueyue
 * @Description: 快速排序
 * @Date: 2024/1/10
 **/
public class QuickSort {
    public static void main(String[] args) {

        // 获取整数数组
        int[] arr = getArr();
        System.out.println("输入的数组为:" + Arrays.toString(arr));

        // 快速排序
        quickSort(arr, 0, arr.length - 1);
        System.out.println("排序后的数组为:" + Arrays.toString(arr));
    }

    /**
     * @Description 快速排序
     * @Param [arr: 数组, left: 左边界, right: 右边界]
     * @return void
     **/
    private static void quickSort(int[] arr, int left, int right) {

        if (left > right) { // 左边不能大于右边
            return;
        }

        int temp = arr[left]; // 基准值
        int i = left, j = right;
        while (i < j) {

            // 找到比基准值更小的数,右边先找(为啥一定要右边先找? - 1 3 2) 防止基准值是最小的数,左边先走,就会把基准值交换掉
            while (arr[j] >= temp && i < j) {
                j--;
            }

            // 找到比基准值更大的数,这边必须设置等于temp,不然左边的数不会走
            while (arr[i] <= temp && i < j) {
                i++;
            }

            // 如果没有相遇,则: i == j 时,没必要交换
            if (i < j) {
                int t = arr[i];
                arr[i] = arr[j];
                arr[j] = t;
            }
        }

        // 基准值归位
        arr[left] = arr[j];
        arr[j] = temp;

        // 这里 i,j 是相等的
        quickSort(arr, left, j-1); // 左半边继续排序
        quickSort(arr, j+1, right); // 右半边继续排序
    }

    /**
     * @Description 获取整数数组
     * @Param []
     * @return int[]
     **/
    private static int[] getArr() {
        Scanner scanner = new Scanner(System.in);

        System.out.print("请输入数字个数:");
        int n = scanner.nextInt();

        int[] arr = new int[n];
        System.out.printf("请输入%d个数, 中间用空格隔开,再回车:", n);
        for (int i = 0; i < n; i++) {
            arr[i] = scanner.nextInt();
        }

        return arr;
    }
}

演示

运行代码,控制台输入可得:

在这里插入图片描述

课后习题

在前两期博客中:

  1. Java玩转《啊哈算法》排序之桶排序

  2. Java玩转《啊哈算法》排序之冒泡排序

我都给出了力扣上面的同一道题,作为课后习题:

912. 排序数组

在这里插入图片描述
但是比较尴尬的是,用桶排序做这道题目,占用内存太多了,仅击败 34.93% 使用Java的用户!

在这里插入图片描述
如果用冒泡排序,那就更尴尬了,直接超时,通过都通过不了:

在这里插入图片描述

但是如果你学会了本文中的快速排序,能不能做到,又快又稳(占用内存少) 呢?

噔噔蹬蹬:

在这里插入图片描述

很遗憾,翻车了,又超时了,我们翻开超时的测试用例可以发现,有非常多的重复元素:

在这里插入图片描述

这也正印证了上文所说的,快速排序的执行速率不稳定,最差的时候跟冒泡排序是一样的。

虽然桶排序算法跟贪心算法一样因为思路都比较简单,所以容易被人轻视。

俗话说,真心(简单)往往留不住,唯有套路(复杂)得人心。

但是桶排序在这道题上面是当之无愧的老大哥!!!

在这里插入图片描述

看来没有最牛逼的算法,只有最合适的算法

当然,快速排序针对多重复元素进行 三路快排 的话, 虽然速率还是比不上桶排序,但是也能通过这道题。

不过这个超纲了,不在本篇博客的讨论范围内了,感兴趣的同学可以去看一下这位朋友的题解:

快速排序优化(针对多重复元素情况)

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

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

相关文章

详解SpringCloud微服务技术栈:深入ElasticSearch(2)——自动补全、拼音搜索

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;详解SpringCloud微服务技术栈&#xff1a;深入ElasticSearch&#xff08;1&#xff09;——数据聚合 &#x1f4da;订阅专栏&…

STM32——温湿度LCD显示并上传手机

STM32——温湿度LCD显示并上传手机 1.LCD1602 硬件接线 D0~D7 – A0~A7 RS – B1 RW – B2 EN – B10 V0 – GND&#xff08;正视看不到显示结果&#xff0c;需要侧着看。否则需要接可调电阻&#xff09; 引脚封装 RS、RW、EN三根信号线经常需要进行拉高/拉低操作&…

JAVAEE初阶 网络编程(八)

IP协议 认识IP协议 在认识IP协议之前&#xff0c;我们首先要明确IP协议的工作范围或者是用途。 &#xff08;1&#xff09; 地址管理&#xff1a;使用一套地址体系&#xff0c;来描述互联网上各个设备所处的为止。 &#xff08;2&#xff09; 路由选择&#xff1a;数据包如何从…

使用linux进程管理工具supervisor管理你的多个应用进程(支持web界面)

前言 supervisor可以帮你管理进程&#xff0c;你只需要编写配置文件&#xff0c;supervisor便可以方便控制启动&#xff0c;暂停&#xff0c;重启某个进程&#xff0c;你可以编写进程启动命令&#xff0c;来控制supervisor要进行的操作 流程 安装 sudo yum update sudo yum…

U2net:Going deeper with nested u-structure for salient object detection

u2net是目前stable-diffusion-webui默认的抠图算法&#xff0c;但是在电商图场景实测下来&#xff0c;效果是很一般的。 1.introduction 1.能否设计一个新的网络用语SOD&#xff0c;允许从头训练&#xff1b;2.保持高分辨率特征图的同时网络更深。U2net是一种为SOD设计的两级…

3671系列矢量网络分析仪

01 3671系列矢量网络分析仪 产品综述&#xff1a; 3671系列矢量网络分析仪产品包括3671C&#xff08;100kHz&#xff5e;14GHz&#xff09;、3671D&#xff08;100kHz&#xff5e;20GHz&#xff09;、3671E&#xff08;100kHz&#xff5e;26.5GHz&#xff09;、3671G&#x…

性能评测工具+数据库主从复制方案

PTS&#xff08;Performance Testing Service&#xff09; 面向所有技术背景人员的云化测试工具 MSQL MGR 8.0 高可用 对性能影响比较大的参数 MyBatis 数据库主从复制 解决方案1 方案2 主从复制经典架构

Gateway API 实践之(六)FSM Gateway 的健康检查功能

FSM Gateway 流量管理策略系列&#xff1a; 故障注入黑白名单访问控制限速重试会话保持健康检查负载均衡算法TLS 上游双向 TLS 网关的健康检查功能是一种自动化监控机制&#xff0c;用于定期检查和验证后端服务的健康状况&#xff0c;确保流量只被转发到那些健康且能正常处理请…

机器学习算法-----K-近邻算法

1.1 K-近邻算法简介 1.定义: 就是通过你的"邻居"来判断你属于哪个类别 2.如何计算你到你的"邻居"的举例 一般时候&#xff0c;都是使用欧氏距离 1.2k近邻算法api初步使用 1.sklearn 优势: 1.文档多&#xff0c;且规范&#xff0c…

谷达冠楠:抖音开店怎么运营好

在数字营销的海洋中&#xff0c;抖音如同一艘快艇&#xff0c;以其独特的视频形式和庞大的用户基础成为商家们的新宠。开店容易&#xff0c;运营难。要想在抖音上成功运营店铺&#xff0c;需要掌握一些核心技巧。 首当其冲的是内容创意。抖音的用户喜好多变&#xff0c;因此&am…

AI 神助攻,协同办公神器 ---- ONLYOFFICE

人工智能不会取代人&#xff0c;只会淘汰那些不会使用人工智能的人。 – 鲁迅 一、人工智能重新定义办公新模式 随着GPT的横空出世&#xff0c;AI的应用场景已经无处不在&#xff0c;从智能客服、智能语音助手、智能家居到自动驾驶汽车等&#xff0c;AI正在不断地拓展其应用领…

关于缓存数据一致性的解决方案

缓存数据一致性 引入缓存会导致一些比如修改/删除内容后缓存还是之前的数据&#xff0c;这会导致缓存和数据库数据不一致的情况&#xff0c;本文将提到相关的解决方案&#xff0c;而且还提供了canal去实现每次在更新数据库的时候自动同步缓存&#xff0c;而无需将代码都写在后…

操作系统基础:进程同步【上】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;OS从基础到进阶 1 进程同步&#xff08;上&#xff09;1.1 进程同步与互斥1.1.1 进程同步1.1.1.1 必要性1.1.1.2 什么是进程同步 1.1.2 进程互斥1.1.2.1 必要性1.1.2.2 什么是进程互斥1.1.…

RTC 滴答计时器

1. RTC 滴答计时器 1.1 寄存器配置 RTCCON RTC控制寄存器 [7:4] 0000 设置频率 [8] 1 使能滴答计时器 TICNT 32位滴答时间计数值。 滴答计时器是一个上行计数器。如果当前的滴答数达到这个值&#xff0c;滴答时间中断发生。 备注:该值必须大于3 周期 (n 1)/滴答时钟…

文件制作二维码的图文教学,多种格式都可以使用

现在我们经常会发现在扫描二维码的时候&#xff0c;可能一个二维码中会存在多个文件或者多个二维码中会显示不同的文件的&#xff0c;那么这些文件存入二维码中是用什么方法制作的呢&#xff1f; 文件二维码的制作方法其实很简单&#xff0c;只需要通过文件二维码生成器工具的…

这都2024年了 你还要多久才能领悟 LinkedList 源码

这都2024年了 你还要多久才能领悟 LinkedList 源码 文章目录 这都2024年了 你还要多久才能领悟 LinkedList 源码LinkedList 简介LinkedList 插入和删除元素的时间复杂度&#xff1f;LinkedList 为什么不能实现 RandomAccess 接口&#xff1f; LinkedList 源码分析初始化插入元素…

【数据结构之二叉树的构建和遍历】

数据结构学习笔记---009 数据结构之二叉树1、二叉树的概念和结构1.1、回顾二叉树的重要性质1.2、回顾二叉树的主要分类1.1、如何实现二叉树&#xff1f; 2、二叉树的实现2.1、二叉树的BinaryTree.h2.2、二叉树的BinaryTree.c2.2.1、二叉树的构建2.2.2、二叉树销毁2.2.3、二叉树…

RabbitMQ入门概念

目录 一、RabbitMQ入门 1.1 rabbitmq是啥&#xff1f; 1.2 应用场景 1.3 AMQP协议与RabbitMQ工作流程 1.4 Docker安装部署RabbitMQ 二、SpringBoot连接MQ配置 2.1 示例1 2.1 示例2 —— 发送实体 一、RabbitMQ入门 1.1 rabbitmq是啥&#xff1f; MQ&#xff08;Message…

Hutool导入导出用法

整理了下Hutool导入导出的简单使用。 导入maven或jar包&#xff08;注意这里导入的poi只是为了优化样式&#xff09; <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all --> <dependency><groupId>cn.hutool</groupId><artifactId&g…

Kube-Promethus配置Nacos监控

Kube-Promethus配置Nacos监控 前置&#xff1a;Kube-Promethus安装监控k8s集群 一.判断Nacos开启监控配置 首先通过集群内部任一节点访问Nacos的这个地址<NacosIP>:端口号/nacos/actuator/prometheus&#xff0c;查看是否能够获取监控数据。 如果没有数据则修改Nacos集群…