class 023 随机快速排序

news2024/11/23 12:25:07

这篇文章是看了“左程云”老师在b站上的讲解之后写的, 自己感觉已经能理解了, 所以就将整个过程写下来了。

这个是“左程云”老师个人空间的b站的链接, 数据结构与算法讲的很好很好, 希望大家可以多多支持左程云老师, 真心推荐.
https://space.bilibili.com/8888480?spm_id_from=333.999.0.0

在这里插入图片描述

1. 快速排序过程详解

1.1 逻辑解释

  1. 先设置一个数组 arr[] = [ ], 下标是:0 ~ (n - 1),
  2. 然后 0 ~ (n - 1) 位置随机选择一个数字:p,
  3. 那么就按 p 为分界线, 将 <= p 的数字放在左边, 将 > p 的数字放在右边,
  4. 然后将 p 这个数字放在左边范围的最右侧位置, (当然可能有很多个 p, 但是我们不关心, 只是放一个)
  5. 此时这个 p 数字算是排好序了. 在整个数组位置上就已经不用变了.
  6. 然后从这个已经排好序的数字 p左边和右边位置调用递归过程排序就行了. (就是重复上面的过程), 每次都能将一个数字排好序.

1.2 代码实例

这个过程和上面的逻辑实现是一样的, 直接看就能看懂, 这里我就直接说明几个需要注意的点.

  1. l >= r:若是 l == r 说明只有一个数字, 不用排序, 若是 l > r 说明数组不存在.
  2. Math.random() 方法的使用:这个 Math.random() 方法返回 [0, 1) 之间任意的一个浮点数, 我感觉说到这里已经能说明问题了, 这个应该是谁都要掌握的, 要是不知道, 就直接随便看一下讲解就行了. 真没有什么好讲的.
public static int[] sortArray(int[] nums) {  
    if (nums.length > 1) {  
       quickSort1(nums, 0, nums.length - 1);  
    }  
    return nums;  
}  
  
// 随机快速排序经典版(不推荐)  
public static void quickSort1(int[] arr, int l, int r) {  
    if (l >= r) {  
       return;  
    }  
    // 随机这一下,常数时间比较大  
    // 但只有这一下随机,才能在概率上把快速排序的时间复杂度收敛到O(n * logn)  
    int x = arr[l + (int) (Math.random() * (r - l + 1))];  
    int mid = partition1(arr, l, r, x);  
    quickSort1(arr, l, mid - 1);  
    quickSort1(arr, mid + 1, r);  
}  
  
// 已知arr[l....r]范围上一定有x这个值  
// 划分数组 <=x放左边,>x放右边,并且确保划分完成后<=x区域的最后一个数字是x  
public static int partition1(int[] arr, int l, int r, int x) {  
    // a : arr[l....a-1]范围是<=x的区域  
    // xi : 记录在<=x的区域上任何一个x的位置,哪一个都可以  
    int a = l, xi = 0;  
    for (int i = l; i <= r; i++) {  
       if (arr[i] <= x) {  
          swap(arr, a, i);  
          if (arr[a] == x) {  
             xi = a;  
          }  
          a++;  
       }  
    }  
    swap(arr, xi, a - 1);  
    return a - 1;  
}  
  
public static void swap(int[] arr, int i, int j) {  
    int tmp = arr[i];  
    arr[i] = arr[j];  
    arr[j] = tmp;  
}

2. partition 函数详解

这段代码会有两个分支, 就是 <= x 和 > x, 所以最开始, 设置 i 对传递进来的数组范围上进行遍历,

  1. 若是 i 遍历到的数字 <= x, 就让 arr[a] 和 arr[i] 进行互换(这一步是到了 i 遍历的数字 > x 的时候进行交换, 为以后做铺垫), 然后将 a++, i++. 此时 <= x 范围的的数字扩大了.
  2. 若是 i 遍历到的数字 > x, 就让 a不变, i++,
  3. 通过上述过程, i 遍历完数组的数字之后, 可以实现将所有 <= x 的数字放在数组的左侧, 将所有 > x 的数字放在数组的右侧.
  4. 然后将 x 数字归位到 a - 1 位置. 因为这样才能保证 x 数字在数组中已经是排好序了.
  5. 最后返回 a - 1 下标.

其中的 a, xi 变量, 左程云老师都在代码的注释中说明好了, 就不画蛇添足了.

// 已知arr[l....r]范围上一定有x这个值  
// 划分数组 <=x放左边,>x放右边,并且确保划分完成后<=x区域的最后一个数字是x  
public static int partition1(int[] arr, int l, int r, int x) {  
    // a : arr[l....a-1]范围是<=x的区域  
    // xi : 记录在<=x的区域上任何一个x的位置,哪一个都可以  
    int a = l, xi = 0;  
    for (int i = l; i <= r; i++) {  
       if (arr[i] <= x) {  
          swap(arr, a, i);  
          if (arr[a] == x) {  
             xi = a;  
          }  
          a++;  
       }  
    }  
    swap(arr, xi, a - 1);  
    return a - 1;  
}  

3. Partition 函数优化

3.1 优化逻辑

在经典的 Partition 函数中, 只是将所有的位置分成了两个区域, 一次只能是调整好一个数字, 但是有可能在一个数组中, 可能有很多个相同的数字, 比如在随机选择之后, 我们选择了 x, 但是这个 x 在这个数组中, 有很多个, 那我们干脆可以将所有与 x 相等的数字都排好序, 所以对应的:可以将这个数组分为三个区域:左边是 < x 的区域, 中间是 == x 的区域, 右边是 > x 的区域, 这样就有可能一次将数组中的多个数字排好序.

3.2 代码实例

讲解:我们随机选择的一个数字:x, 还是和原来一样, 设置一个 i 遍历数组中的所有数字, 将传进来的数组的最左边设置为 l, 最右边设置为:r, 分为三种情况.

  1. 若是 < x, 则交换 a 和 i, 然后将 a++, i++.
  2. 若是 == x, 则只是 i++.
  3. 若是 > x, 则交换 b 和 i, 然后将 b--, i 不变.
  4. 按照上述步骤, 可以将数组分为三个部分.

这个的时间复杂度为:O(n), 因为只是用 i 遍历了整个数组.

需要注意的是:这个函数是有返回值的, 只是用全局变量进行等效果的替换了. 使用的方式和意义及其注意点左程云老师都在注释里说明白了.

// 随机快速排序改进版(推荐)  
public static void quickSort2(int[] arr, int l, int r) {  
    if (l >= r) {  
       return;  
    }  
    // 随机这一下,常数时间比较大  
    // 但只有这一下随机,才能在概率上把快速排序的时间复杂度收敛到O(n * logn)  
    int x = arr[l + (int) (Math.random() * (r - l + 1))];  
    partition2(arr, l, r, x);  
    // 为了防止底层的递归过程覆盖全局变量  
    // 这里用临时变量记录first、last  
    int left = first;  
    int right = last;  
    quickSort2(arr, l, left - 1);  
    quickSort2(arr, right + 1, r);  
}  
  
// 荷兰国旗问题  
public static int first, last;  
  
// 已知arr[l....r]范围上一定有x这个值  
// 划分数组 <x放左边,==x放中间,>x放右边  
// 把全局变量first, last,更新成==x区域的左右边界  
public static void partition2(int[] arr, int l, int r, int x) {  
    first = l;  
    last = r;  
    int i = l;  
    while (i <= last) {  
       if (arr[i] == x) {  
          i++;  
       } else if (arr[i] < x) {  
          swap(arr, first++, i++);  
       } else {  
          swap(arr, i, last--);  
       }  
    }  
}

4. 时间复杂度分析

在这里插入图片描述

这个随机行为的时间复杂度需要用期望来估计, 这个已经在前面的时间复杂度章节中说明过了.

4.1 最差情况

最慢的情况是:arr[] = [1, 2, 3, 4, 5, 6, 7], 若是选择了一个最右侧的数字 7, 那就要将左边所有的数字遍历一遍, 将 7 放到正确位置之后, 选择 6, 继续进行遍历, 最后都排好序了, 这个的时间复杂度是:O(n^2).

空间复杂度是:O(n), 因为按照最差情况, 递归过程压的层数是 n 层.

4.2 最优情况

最优情况是随机选择之后的数字在整个数组的最中间位置(按数值大小). 所以递归过程是两个相同的子过程 (近似看成), 然后 Partition 过程的时间复杂度是:O(n), 所以根据 master 公式,
时间复杂度 == 2 * T(N/2) + O(n) == O(n * log(n)).

空间复杂度:因为此时是最优的情况, 所以对应的递归过程也是一个最好的情况, 递归到最底层之后, 返回, 然后进行另一个递归过程, 此时占用的空间是原来最底层的函数使用过的, 是重复利用了的空间, 所以假设数组长度是:n, 所以这个就是一个二分的过程, 空间复杂度:O(log(n)).

当然也有可能是一般情况, 有可能还是比较靠近两侧, 或者是比较靠近中间, 但不是最优情况, 也不是最差情况.

所以根据期望, 最后的时间复杂度是:O(n * log(n)), 空间辅助度是:O(log(n)). 而且这个不是最好情况的时间复杂度和空间复杂度, 这是数学家们根据所有的情况统计之后, 进行严密的论证之后的答案, 是可以和最好的情况相等, 但是不是最好情况直接用的.

注意:这个证明过程很复杂, 不用掌握也不影响后续的学习.

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

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

相关文章

云中红队系列 | 使用 AWS API 配置Fireprox进行 IP轮换

在渗透测试评估期间&#xff0c;某些活动需要一定程度的自动化&#xff0c;例如从 LinkedIn 等网站抓取网页以收集可用于社会工程活动、密码喷洒登录门户或测试时盲注的有效员工姓名列表网络应用程序。但是&#xff0c;从单个源 IP 地址执行这些活动可能会导致在测试期间被拦截…

【TabBar嵌套Navigation案例-新特性页面-代码位置 Objective-C语言】

一、接下来,我们来说这个新特性页面 1.首先,看一下我们的示例程序,这里改一下,加一个叹号, command + R, 好,首先啊,这里边有一个新特性页面,当我这个程序是第一次安装、第一次运行、还有呢、就是当这个应用程序更新的时候,我应该去加载这个新特性页面, 然后呢,这…

JPEG图像的DCT(Discrete Cosine Transform)变换公式代码详解

引 言 网络上图像在传输过程中为节省内存空间主要采用jpeg格式。jpeg图属于有损压缩图像的一种。在图像篡改检测过程中&#xff0c;可以利用jpeg图像的单双压缩伪影的不同而判别图像为伪造图并可以定位伪造区域。RGB图像变成jpeg图像过程中涉及从RGB图变成YCbCr图像&#xff0c…

使用离火插件yoloV8数据标注,模型训练

1. 启动 2.相关配置 2.1 data.yaml path: D:/yolo-tool/yaunshen-yolov8/YOLOv8ys/YOLOv8-CUDA10.2/1/datasets/ceshi001 train: images val: images names: [蔡徐坤,篮球] 2.2 cfg.yaml # Ultralytics YOLOv8, GPL-3.0 license # Default training settings and hyp…

物联网行业中通信断线重连现象介绍以及如何实现

01 概述 断线重连是指在计算机网络中&#xff0c;当网络连接遇到异常中断或者断开时&#xff0c;系统会自动尝试重新建立连接&#xff0c;以保证网络通信的连续性和稳定性。这是一种常见的网络通信技术&#xff0c;广泛应用于各种计算机网络场景&#xff0c;包括互联网、局域…

蓝队技能-应急响应篇Web内存马查杀Spring框架型中间件型JVM分析Class提取

知识点&#xff1a; 1、应急响应-Web框架内存马-分析&清除 2、应急响应-Web中间件内存马-分析&清除 注&#xff1a;框架型内存马与中间件内存马只要网站重启后就清除了。 目前Java内存马具体分类&#xff1a; 1、传统Web应用型内存马 Servlet型内存马&#xff1a;…

探索EasyCVR视频融合平台:在视频编解码与转码领域的灵活性优势

随着视频监控技术的飞速发展&#xff0c;各类应用场景对视频数据的处理需求日益复杂多样。从公共安全到智慧城市&#xff0c;再到工业监控&#xff0c;高效、灵活的视频处理能力成为衡量视频融合平台性能的重要标准。在众多解决方案中&#xff0c;EasyCVR视频融合平台凭借其在视…

Java面试题之JVM20问

1、说说 JVM 内存区域 这张图就是一个 JVM 运行时数据图&#xff0c;「紫色区域代表是线程共享的区域」&#xff0c;JAVA 程序在运行的过程中会把他管理的内存划分为若干个不同的数据区域&#xff0c;「每一块儿的数据区域所负责的功能都是不同的&#xff0c;他们也有不同的创建…

Django设计批量导入Excel数据接口(包含图片)

Django设计批量导入Excel数据接口(包含图片) 目录 Django设计批量导入Excel数据接口(包含图片)示例xlsx文件接口详情前端上传FormData后端APIView调用函数 Django 4.2.7 openpyxl 3.1.5示例xlsx文件 接口详情 前端上传FormData …

2-104 基于MATLAB的动态模式分解(Dynamic Mode Decomposition,DMD)

基于MATLAB的动态模式分解&#xff08;Dynamic Mode Decomposition&#xff0c;DMD&#xff09;,从人类步行数据中提取信息.动态模式分解是一种降维算法&#xff0c;在流体力学领域引入的。与提供内部坐标系和相应投影的SVD相似&#xff0c;DMD为您提供随不同时间行为演变的特定…

【架构】前台、中台、后台

文章目录 前台、中台、后台1. 前台&#xff08;Frontend&#xff09;特点&#xff1a;技术栈&#xff1a; 2. 中台&#xff08;Middleware&#xff09;特点&#xff1a;技术栈&#xff1a; 3. 后台&#xff08;Backend&#xff09;特点&#xff1a;技术栈&#xff1a; 示例场景…

PMOS的原理

PMOS&#xff08;金属氧化物半导体场效应晶体管&#xff09;是一种以空穴为主要载流子的场效应管&#xff0c;它的D极&#xff08;漏极&#xff09;、S极&#xff08;源极&#xff09;和G极&#xff08;栅极&#xff09;的工作原理如下&#xff1a; 1. D极&#xff08;漏极&am…

已存在的Python项目使用依赖管理工具UV

1. 文档 uv文档 2. 如何转换 初始化 uv initrequirements.txt转换成pyproject.toml uv add $(cat requirements.txt)删除requirements.txt 如果更新pyproject.toml之后&#xff0c;使用命令 uv sync替换项目环境 如果有库没有加入依赖&#xff0c;自己手动加一下&am…

详解电力物联网通常使用哪些通信规约?

在电力物联网行业中&#xff0c;通信规约是关键的技术之一&#xff0c;用于实现电网设备与控制中心之间的数据通信和信息管理。本篇就为大家简单说明电力物联网通常使用哪些通信规约。 1、IEC 60870-5-101/104 这是由国际电工委员会&#xff08;IEC&#xff09;制定的一系列标…

微信小程序配置prettier+eslint

虽然微信开发者工具是基于vscode魔改的.但是由于版本过低,导致很多插件也用不上新版本.所以在微信开发者工具限制的版本下使用的prettier,eslint也是有版本要求. 本文主要就是记录一下需要的版本号 1.微信开发者工具安装插件 2.package.json中添加以下依赖及安装依赖 "de…

【HarmonyOS】组件长截屏方案

【HarmonyOS】普通组件与web组件长截屏方案&#xff1a;原则是利用Scroll内的组件可以使用componentSnapshot完整的截屏 【普通组件长截屏】 import { componentSnapshot, promptAction } from kit.ArkUI import { common } from kit.AbilityKit import { photoAccessHelper }…

增量式编码器实现原理

目录 概述 1 认识增量式编码器 1.1 概述 1.2 增量式编码器的特性 1.3 编码器的硬件 2 增量式编码器实现原理 2.1 编码器信号 2.2 正反转判断 概述 本文主要介绍增量式编码器实现原理&#xff0c;包括增量式编码器的特性&#xff0c;信号特性&#xff0c;以及如何使用编…

【稳定且高效的分治排序 —— 归并排序算法】

【稳定且高效的分治排序 —— 归并排序算法】 归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效、稳定的排序算法&#xff0c;采用分治法的典型应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff0c;即先使每个子序列有序&#xff0…

深度探索与实战编码:利用Python与AWS签名机制高效接入亚马逊Product Advertising API获取商品详情

亚马逊商品详情接口技术贴及代码示例 在电商数据分析和产品管理中&#xff0c;获取商品的详细信息是至关重要的一环。亚马逊作为全球领先的电商平台&#xff0c;提供了丰富的商品数据和强大的API接口&#xff0c;使得开发者能够轻松获取商品的详细信息。本文将详细介绍如何使用…

NASA:ATLAS/ICESat-2 L3 A沿线内陆地表水数据V006数据集

目录 简介 代码 引用 网址推荐 0代码在线构建地图应用 机器学习 ATLAS/ICESat-2 L3A Along Track Inland Surface Water Data V006 简介 ATLAS/ICESat-2 L3 A沿线内陆地表水数据V006 ATLAS/ICESat-2 L3 A沿线内陆地表水数据V006是指由ATLAS/ICESat-2卫星获取的针对陆地…