引言

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

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

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

一、快速排序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)。

排序算法——随机快速排序相关推荐

  1. 十大排序算法之快速排序(两种方法)

    十大排序算法之快速排序 本文采用Java书写选择排序,其他语言类似可以借鉴着写 思想:在待排序序列中选择一个分割元素,将待排序序列中所有比分割元素关键字小的元素移动到分割元素左侧位置:将待排序序列中所 ...

  2. 排序算法之----快速排序(快速上手快速排序)

    排序算法之----快速排序(快速上手快速排序) 何为快速排序算法? 快速排序的基本思想又是什么? 其实很简单: 快速排序的基本思想是 1.先从数列中取出一个数作为基准数(这里我们的算法里面取数组最右边 ...

  3. 【排序算法】快速排序(C语言)

    [排序算法]-- 快速排序 目录 一.快速排序的单趟排序 1. 霍尔法 2. 挖坑法 3. 前后指针 二.快速排序 1. 排序步骤 2. 排序完整步骤图 3. 快速排序代码 3.1 递归实现 3.2 ...

  4. php1到5000排序,常用的排序算法(一)--快速排序(PHP实现)

    常用的排序算法系列 快速排序 假设当前需要从小到大进行排序,快速排序的核心思路是,从当前数组中,找到一个元素作为基准比较值(key),分别从两个方向进行比较.从后往前找,比key小元素放在数组前面.然 ...

  5. 排序算法(5)快速排序

    排序算法(5)快速排序 思想:递归,分治法. 1.先从数列中取出一个数作为基准数. 2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边. 3.再对左右区间重复第二步,直到各区 ...

  6. 排序算法-04快速排序(Python实现)

    快速排序 性质 一种基本的交换排序算法,比较常用的排序算法,简称快排. 基本的排序思路如下.基本思想为:通过一趟排序将要排序的数据分割成独立的两部分,分割点左边的数都比它小,分割点右边的数都比它大,然 ...

  7. 排序算法之快速排序详解

    一.算法介绍 快速排序:快速排序的基本思想是通过一次排序将等待的记录分成两个独立的部分,其中一部分记录的关键字小于另一部分的关键字.C部分的快速排序一直持续到整个序列被排序. 任取一个元素 (如第一个 ...

  8. 排序算法:快速排序算法实现及分析(递归形式和非递归形式)

    快速排序算法介绍 从名字上就可以看出快速排序算法很嚣张,直接以快速命名.确实快速排序 的确很快速,被列为20世纪十大算法之一.程序员难道不应该掌握么.快速排序(Quick Sort)的基本思想是:通过 ...

  9. 排序算法(4)----快速排序

    快速排序由C. A. R. Hoare在1962年提出,它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分 ...

最新文章

  1. 零基础在学习Java时如何才能打好基础呢
  2. 使用DPM还原exchange 2013已删除邮箱数据
  3. 前端笔试能查吗_老码农的字节跳动前端面试总结
  4. c++学习笔记之输入/输出流
  5. Codeforces 138C(区间更新+离散化)
  6. freemarker结合springMVC配置
  7. DirectX Audio和DirectShow入门
  8. 移动端问题列表及解决方案
  9. NLP大牛菲利普•科恩机器翻译权威著作
  10. mac u盘linux 双系统安装教程,苹果电脑怎么安装双系统 苹果电脑安装双系统教程...
  11. Linux命令之压缩gzip
  12. Rayson API 框架分析系列之6: 客户端动态代理原理
  13. 解决“Macbook外接显示器后键盘音量键无法使用以及Numlock失效”的问题
  14. Pyinstaller:moviepy打包报错AttributeError: module ‘moviepy.audio.fx.all‘ has no attribute ‘audio_fadein‘
  15. Android点击打开微信
  16. webstorm2020.2.3下载安装教程
  17. python永久删除文件_Python彻底删除文件夹及其子文件方式
  18. JavaSE探赜索隐之乾坤袋(集合)
  19. [Spark版本更新]--2.3.0发行说明(一)
  20. 量化金融分析AQF(5):金融数据获取、清洗、整理和存储(Yahoo、Tushare)

热门文章

  1. da---tlc5615._CD-DA的完整形式是什么?
  2. 数组中的reverse_数组reverse()方法以及JavaScript中的示例
  3. 第 5-6 课:Java 并发包中的高级同步工具 + 面试题
  4. Spring整合JDBC开发
  5. 计算机ppt文字1是什么原因,ppt让答案一个个出现,ppt让文字一个个出现
  6. linux 动态库构造函数,Linux共享库全局构造函数的相互依赖性
  7. php+页面加载进度,基于jQuery实现模拟页面加载进度条_jquery
  8. tomcat 启动项目 页面文字乱码_项目通过tomcat部署到服务器,请求数据页面中文乱码问题...
  9. gprs模块ftp 远程升级_基于GPRS无线通信技术的冷链监测系统
  10. @data注解不生效_你说啥什么?注解你还不会?