排序算法——随机快速排序

引言

随机快排是一个非常有意思的排序排序算法,它的算法思想用到了如递归、荷兰国旗问题等诸多元素,还意外的引入了随机性的概念。

以下将逐步总结三个版本的快速排序,由浅入深总结快速排序的经典实现过程。

荷兰国旗问题参考:《荷兰国旗问题》

一、快速排序1.0

在荷兰国旗问题中,我们通过简单的逻辑可以将一个数组分为两个区域或三个区域,但往往需要在题目之初给定一个 target 作为目标数以此划分。

而在快速排序算法中,这个 target 选为排序范围上的最右边的数——arr[R],我们以 R 上的数为 target 将R位置左侧先划分为两块区域 小于等于区和大于区,最后再将 R 与 大于区域的第一个数交换就可以完成整体的划分。

通过上面的这个主过程,我们通过递归,将左右两部分递归完成上面的过程,最终就可以实现整个数组的有序。

注意,在具体编码时,由于递归每次都需要选出一个数作为基准数,依此划分两块区域,在这个版本中,我们以 R 位置上的数为基准数,以此来划分,循环完毕后,我们只实现了R位置左侧的内容变为小于等于区和大于区,这时必须在循环外部额外单独处理一次基准数,做法就是将它与小于等于区的右边+1位置交换,并将小于等于区扩大一个位置即可。

    private static int partition(int[] arr, int L, int R) {if (L > R)return -1;if (L == R)return L;int lessEqual = L - 1;int cur = L;while (cur < R) {if (arr[cur] <= arr[R])SortUtil.swap(arr, ++lessEqual, cur);// 这里必须写在 if 外面自加,如果写在传参处自加,会导致大于时无法自加cur++;}SortUtil.swap(arr, ++lessEqual, R);return lessEqual;}

最后通过递归完成划分区域的步骤:

    public static void quickSort1(int[] arr) {// 注意不要写成 lenth == 0if (arr == null || arr.length < 2)return;process1(arr, 0, arr.length - 1);}private static void process1(int[] arr, int L, int R) {if (L >= R)return;int M = partition(arr, L, R);process1(arr, L, M - 1);process1(arr, M + 1, R);}

说明一下 M,它代表一个[L, R]范围上的 中点位置,取自 partition 方法的返回值。partition 方法将 L R 范围上的数组划分成两个区域——小于等于区和大于区,M 即为上一次分区划分的基准数。

就1.0版本的快排而言,由于每次基准数都取自 R 位置上的数,那么思考这样一个问题,最理想的基准数和最差基准数分别是怎样的情况呢?

如果可以将 [L, R] 范围上的数充分等分,那么这就是最理想的基准数,如果范围上的数都偏向基准数一侧,那么就可能使算法花费更长的执行时间。

二、快速排序2.0

在1.0 中,我们选取基准数将[L, R]范围上的数划分为两部分,但是对于基准数重复的情况,它可能会造成在小于等于区不连续的情况,导致可能需要重复选择相同基准数的问题。

对于这个问题,通过荷兰国旗问题(三块分)的方法可以很好的优化。

    /** 快排2.0*/public static void quickSort2(int[] arr) {if (arr == null || arr.length < 2)return;process2(arr, 0, arr.length - 1);}/** 迭代处理*/private static void process2(int[] arr, int L, int R) {if (L >= R)return;int[] midRange = Code2_NetherlandsFlag.netherlandsFlag(arr, L, R, arr[R]);process2(arr, L, midRange[0] - 1);process2(arr, midRange[1] + 1, R);}/*** 荷兰国旗划分*/public static int[] netherlandsFlag(int[] arr, int L, int R, int target) {if (L > R)return new int[]{-1, -1};if (L == R)return new int[]{L, R};int less = L - 1;int more = R + 1;int curr = L;while (curr < more) {if (arr[curr] == target) {curr++;} else if (arr[curr] < target) {SortUtil.swap(arr, curr, less + 1);less++;curr++;} else {SortUtil.swap(arr, curr, more - 1);more--;}}// 等于区域的左边界和右边界 [less + 1, more - 1]return new int[]{less + 1, more - 1};}

三、快速排序 3.0 —— 随机快速排序

在1.0的版本中提到,当 R 位置选取的基准数可以比较好的中分数组元素,那么递归函数处理两侧的时间就会大致均分,从而可以达到 O(N * logN) 时间复杂度,而为了更好的达到这一点,在 2.0 基础之上,我们需要随机选取数组上的一个元素,将其视为基准数,然后其他的内容基本和 2.0 没有任何区别。

注意,在随机快排的递归进行前选取基准数的时候,我们依然选取 R 位置上的数,只不过我们会先随机一个位置,将其换到 R 位置上,在进行递归处理:

完整代码如下:

    /** 快排3.0*/public static void quickSort3(int[] arr) {if (arr == null || arr.length < 2)return;process3(arr, 0, arr.length - 1);}private static void process3(int[] arr, int L, int R) {if (L >= R)return;// 随机选取1个位置上的数换到 R 位置上SortUtil.swap(arr, L + (int) Math.random() * (R - L + 1), R);int[] midRange = Code2_NetherlandsFlag.netherlandsFlag(arr, L, R, arr[R]);process2(arr, L, midRange[0] - 1);process2(arr, midRange[1] + 1, R);}public static int[] netherlandsFlag(int[] arr, int L, int R, int target) {if (L > R)return new int[]{-1, -1};if (L == R)return new int[]{L, R};int less = L - 1;int more = R + 1;int curr = L;while (curr < more) {if (arr[curr] == target) {curr++;} else if (arr[curr] < target) {SortUtil.swap(arr, curr, less + 1);less++;curr++;} else {SortUtil.swap(arr, curr, more - 1);more--;}}// 等于区域的左边界和右边界 [less + 1, more - 1]return new int[]{less + 1, more - 1};}

四、随机快排的时间复杂度

在每次随机选取基准数时,每个数的选中概率是 1/N ,根据数学家的证明,随机快排的时间复杂度会收敛于 O(N * logN),它并不代表不会出现 O(N ^ 2) 的情况,而是将最坏的情况变成了随机概率事件。

总结

快速排序借助了几个非常重要的算法技巧:迭代、荷兰国旗问题、随机概率性。

它的时间复杂度会收敛于 O(N * logN)。

 

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

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

相关文章

Redis 基础——五大类型与数据结构

引言 Redis 区分于 memcahced 的一个重要不同就是它具有明确的类型概念&#xff0c;在Redis 的使用过程中&#xff0c;都离不开这些类型的学习&#xff0c;它不仅是 Redis 能力的基础&#xff0c;同时也是一些重要数据结构和算法思想的体现。 本博客总结了五大类型的书面重点…

比较器的使用

一、TreeMap 实现排序 TreeMap是一个有序结构&#xff0c;TreeSet也是类似。 他们可以实现对元素的排序。TreeMap 是针对 key进行排序。 如果TreeMap的key 是Integer类型&#xff0c;可以无需指定任何特殊条件&#xff0c;默认即按照升序进行排序&#xff0c;如&#xff1a;…

经典数据结构——堆的实现

一、完全二叉树 堆是一种完全二叉树&#xff0c;什么是完全二叉树&#xff1f; 简单的说&#xff0c;一棵满二叉树表示的是所有节点全部饱和&#xff0c;最后一层全部占满&#xff1a; 而完全二叉树指的是满二叉树的最后一层&#xff0c;所有叶子节点都从左往顺序排满&#x…

排序算法 —— 堆排序

引言 此文基于《经典数据结构——堆的实现》中堆结构&#xff0c;实现一个以堆处理排序的算法。 一、算法思想 基于堆结构的堆排序的算法思想非常简单&#xff0c;循环获取大根堆中的最大值&#xff08;0位置的根节点&#xff09;放到堆的末尾&#xff0c;直到将堆拿空。 由…

经典数据结构——前缀树

引言 前缀树——trie /ˈtraɪ//树&#xff0c;也叫作“单词查找树”、“字典树”。 它属于多叉树结构&#xff0c;典型应用场景是统计、保存大量的字符串&#xff0c;经常被搜索引擎系统用于文本词频统计。它的优点是利用字符串的公共前缀来减少查找时间&#xff0c;最大限度…

排序算法 —— 计数排序

引言 计数排序是桶排序思想的一种具体实现&#xff0c;针对一些具有特殊限制的样本数据&#xff0c;如公司员工年龄&#xff0c;那么样本数据本身就一定在0~200之间&#xff0c;针对这样的数据&#xff0c;使用从0到200 的桶数组&#xff0c;桶的位置已经是有序的&#xff0c;…

Java多线程 —— 线程状态迁移

引言 线程状态迁移&#xff0c;又常被称作线程的生命周期&#xff0c;指的是线程从创建到终结需要经历哪些状态&#xff0c;什么情况下会出现哪些状态。 线程的状态直接关系着并发编程的各种问题&#xff0c;本文就线程的状态迁移做一初步探讨&#xff0c;并总结在何种情况下…

Java中的Unsafe

Java和C语言的一个重要区别就是Java中我们无法直接操作一块内存区域&#xff0c;不能像C中那样可以自己申请内存和释放内存。Java中的Unsafe类为我们提供了类似C手动管理内存的能力。 Unsafe类&#xff0c;全限定名是sun.misc.Unsafe&#xff0c;从名字中我们可以看出来这个类对…

Java 写时复制容器 —— CopyOnWriteArrayList

引言 写时复制的含义是当容器发生修改操作时&#xff0c;如add() 等&#xff0c;就会将原来的容器整体复制一份&#xff0c;这个过程是加锁的。而如果只是读取资源&#xff0c;例如 get() &#xff0c;就不会受到任何同步要求的限制。 写时复制的理念是&#xff0c;如果多个读…

arm中断保护和恢复_浅谈ARM处理器的七种异常处理

昨天的文章&#xff0c;我们谈了ARM处理器的七种运行模式&#xff0c;分别是&#xff1a;用户模式User(usr)&#xff0c;系统模式System(sys)&#xff0c;快速中断模式(fiq)&#xff0c;管理模式Supervisor(svc)&#xff0c;外部中断模式(irq)&#xff0c;数据访问中止模式Abor…

Queue —— JUC 的豪华队列组件

目录引言一、Queue 的继承关系1.1 Queue 定义基础操作1.2 AbstractQueue 为子类减负1.3 BlockingQueue 阻塞式Queue1.4 Deque 两头进出二、Queue 的重要实现三、BlockingQueue 的实现原理四、Queue 在生产者消费者模式中的应用五、Queue 在线程池中的应用六、ConcurrentLinkedQ…

daad转换器实验数据_箔芯片电阻在高温应用A/D转换器中的应用

工业/应用领域高温&#xff1a;地震数据采集系统、石油勘探监测、高精度检测仪产品采用&#xff1a;V5X5 Bulk Metal (R) Foil芯片电阻案例介绍TX424是一个完整的4通道24位模数转换器&#xff0c;采用40脚封装。该设计采用最先进设计方案&#xff0c;两个双通道24位调节器和一个…

excel分段排序_学会这个神操作,报表填报不再五花八门,效率远超Excel

在报表工作人员的的日常工作中&#xff0c;常常要面临统计混乱的终端用户输入的问题。由于无法准确限制用户的输入内容&#xff0c;所以在最终进行数据统计时&#xff0c;常常会出现数据不合法的情况。为此需要花费大量的人力和时间核对校验数据。举个简单的例子&#xff0c;某…

IDEA——必备插件指南

目录一、Free-Mybatis-Plugin二、Lombok三、jclasslib Bytecode Viewer一、Free-Mybatis-Plugin 二、Lombok 三、jclasslib Bytecode Viewer 学习 class 文件的必备插件。 使用简单&#xff0c;安装后可以在菜单 View 中看到 show bytecode with jclasslib&#xff1a; 效果…

jitter 如何优化网络_如何做好关键词优化网络?

越来越多的传统企业开始建立自己的网站&#xff0c;进而不断的推广自己的产品。为了能够让自己的企业网站出现在搜索引擎的首页&#xff0c;现在最常用的手段就是竞价排名和关键词优化网络。往往很多企业会选择关键词优化网络这种方式来推广自己的网站&#xff0c;对于新手seoe…

python学生名片系统_Python入门教程完整版400集(懂中文就能学会)快来带走

如何入门Python&#xff1f;权威Python大型400集视频&#xff0c;学了Python可以做什么&#xff1f;小编今天给大家分享一套高老师的python400集视频教程&#xff0c;里面包含入门进阶&#xff0c;源码&#xff0c;实战项目等等&#xff0c;&#xff0c;不管你是正在学习中&…

JVM——详解类加载过程

导航一、过程概述二、Loading2.1 类加载器2.2 双亲委派机制2.3 类在内存中的结构三、Linking四、Initializing一、过程概述 java 源文件编译后会生成一个 .class文件存储在硬盘上。 在程序运行时&#xff0c;会将用到的类文件加载到 JVM 内存中。从磁盘到内存的过程总共分为三…

pkpm板按弹性计算还是塑性_[转载]双向板按弹性还是按塑性方法计算

双向板按弹性方法还是按塑性方法计算茅老师您好&#xff01;想请教您个问题&#xff0c;PKPM计算双向板时一般都是按弹性算吧&#xff0c;可我去年刚进设计院的时候有一个项目是按塑性算的&#xff0c;这样影响大不大啊&#xff0c;支座与跨中弯矩比值系数取得默认的1.8&#x…

Java 的混合执行模式

导航解释执行与编译执行总结解释执行与编译执行 Java 虽然是先编译再运行&#xff0c;但实际上&#xff0c;对于 JVM 来说&#xff0c;依然是逐条解释执行字节码文件中的指令&#xff0c;即大部分情况下&#xff0c;Java 都是解释执行的。 JVM通过 interpreter 解释器解释执行…

下载 Java 学习的权威文档

JVMS 和 JLS 文档的下载 快速直达&#xff1a; https://docs.oracle.com/javase/8/ --> Java Language and Virtual Machine Specifications jvm specification 和 java language specification 是Java 学习的两个最权威的文档。如果你用的是 Java 8&#xff0c;就可以去下载…