快速排序详解

前言

    快排是不稳定的排序,快排的适用场景是无序的序列,例如此时有一个数组是有序的 / 逆序的,此时的快排效率是最慢的。

过程:

    找一个基准值,找的过程就以挖坑法的方式填坑,第一次排序以挖坑发填完坑之后,以基准值为界限,划分左边和右边,划分完成之后继续以递归的方式挖坑然后划分成左边和右边... 一直循环这个过程,直到划分的子区间只剩下一个元素(数组中只有一个元素就是有序的)

一、挖坑法

    在给划分后的子区间进行排序的时候。首先要有左右的区间界限,所以函数头的设计就是 数组 + 左区间 + 右区间 (都是下标的形式),挖坑法的过程如下:

  挖坑法代码实现:

public static void quickSort(int[] arr) {quick(arr, 0, arr.length - 1);}private static void quick(int[] arr, int start, int end) {if (start >= end) return;int pivot = partition2(arr, start, end);quick(arr, start, pivot - 1);quick(arr, pivot + 1, end);}// 挖坑法private static int partition(int[] arr, int left, int right) {int tmp = arr[left];while (left < right) {// 此时一定要取等号,否则会进入死循环while (left < right && arr[right] >= tmp) right--;arr[left] = arr[right];while (left < right && arr[left] <= tmp) left++;arr[right] = arr[left];}arr[left] = tmp;return left;}

二、hoare 法

 haore 法实现快排:

// hoare 法private static int partition2(int[] arr, int left, int right) {int tmp = arr[left];int i = left;while (left < right) {while (left < right && arr[right] >= tmp) right--;while (left < right && arr[left] <= tmp) left++;swap(arr, left, right);}swap(arr, left, i);return left;}

三、快排的问题以及优化

1. 为什么都是先走右边的指针,而不是先走左边的指针?

    如果是先走左边的 l 指针,此时左边的指针是找比基准值大的,先走左边后走右边,在左右指针相遇时,如果当前左右指针的值比基准值大,然后要和基准值交换位置,就会把大的值换到左边,而较小的基准值换到了后面,不符合排序的要求。

2. 代码中里层 while 循环条件必须写 “=” ?

    一定要写等号,如果不写可能会造成死循环,如果数组中有值相等的元素,如果不写等号,也就是循环就进不去,直接交换,此时交换的是两个相等的元素,然后再次循环下来的时候左右指针都没有变化,此时比较的还是这两个相等的元素,就造成了死循环。

3. 针对栈溢出问题?

     如果元素是有序的,此时就相当于一颗单分支的二叉树,如果树的高度很高,此时就需要递归很多次才能结束,但是此时栈帧是有限的,就很容易造成栈溢出。

4. 优化快排

    上述代码有两个问题:1. 栈溢出, 2. 如果数组有序或者数组是逆序的,时间复杂度会达到O(N)。所以针对快排代码可以进一步优化。

(1)三数取中法

private static int midThree(int[] arr, int left, int right) {int mid = (left + right) / 2;if (arr[left] < arr[right]) {if (arr[mid] < arr[left]) return left;else if (arr[mid] > arr[right]) return right;else return mid;} else {if (arr[mid] > arr[left]) return left;else if (arr[mid] < arr[right]) return right;else return mid;}}

     上述代码的逻辑就是在数组开始位置,数组中间位置,还有结束位置分别取三个数,然后找出这三个数的中位数,以这个数为基准(开始没有优化的时候是以数组开始位置的元素为基准,这里有一个缺陷,就是如果这个元素刚好是数组中最大的元素或者是最小的元素,此时的二叉树就是一个单分支的树),去进行递归,这样可以保证数组中的元素尽量是一颗满二叉树 / 完全二叉树,这样就可以减少递归的次数。

(2)剩余元素少的时候直接用插入排序

    可以看到递归的过程就是将数组一步一步分割形成一颗二叉树的过程,如果是一颗二叉树,此时节点数量从上到下是呈指数的形式增长的,所以最后两层的节点数量是最多的,所以递归的次数也是最多的,但是在排序过程中一定是越排越有序的,其实当剩下最后两层节点的时候,此时的数组已经是趋于有序的了,前一篇文章讲过,一个序列趋于有序的时候,用插入排序是最快的,时间复杂度是 O(N),所以快排还可以进一步的优化就是当元素剩余少的时候,用插入排序的方式来排序,此时就减少了递归的次数,也是一个有效的优化的方法。

    注:不能把插入排序直接拿过来用,现在是有区间的进行排序而不是对整个数组排序,所以函数头中还需要有数组的左右区间

// 进一步优化:当递归到只剩下后两层的节点时,此时这部分剩下的数据已经接近有序了,所以此时可以// 插入排序是最快的, 但是是区间内进行插入排序,所以要指定一个区间public static void insertSort2(int[] arr, int left, int right) {for (int i = left + 1; i <= right; i++) {int tmp = arr[i];int j = i - 1;for (; j >= left; j--) {if (arr[j] > tmp) {arr[j + 1] = arr[j];} else {// arr[j + 1] = tmp;break;}}arr[j + 1] = tmp;}}// 优化之后排序的逻辑
private static void quick(int[] arr, int start, int end) {if (start >= end) return;// 使用这个优化主要是减少递归的次数if (end - start + 1 <= 14) {// 插入排序insertSort2(arr, start, end);}int index = midThree(arr, start, end);// 找到这个下标之后交换 start 和 index 位置的元素即可swap(arr, index, start);int pivot = partition2(arr, start, end);quick(arr, start, pivot - 1);quick(arr, pivot + 1, end);}

四、非递归实现快排

// 非递归实现快速排序/** 过程:先找一次基准,找完之后将基准左边的 左右区间 和 右边的 左右区间 进栈,* 之后将分别弹出左右区间再去找基准,找的过程中需要判断当前基准的左右区间的元素格式是否 >= 2* (如果只有一个元素 / 没有元素,此时就不用排序了)*/public static void quickSort2(int[] arr) {Deque<Integer> stack = new LinkedList<>();int left = 0;int right = arr.length - 1;int pivot = partition(arr, left, right);// 左边有两个元素的情况if (pivot > left + 1) {stack.push(left);stack.push(pivot - 1);}// 右边有两个元素的情况if (pivot < right - 1) {stack.push(pivot + 1);stack.push(right);}while (!stack.isEmpty()) {right = stack.poll();left = stack.poll();pivot = partition(arr, left, right);// 找完基准之后检查左右两边有否还有两个或以上的元素,如果有此时继续找进栈// 之后循环出栈再找基准// 左边有两个元素的情况if (pivot > left + 1) {stack.push(left);stack.push(pivot - 1);}// 右边有两个元素的情况if (pivot < right - 1) {stack.push(pivot + 1);stack.push(right);}}}

 

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

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

相关文章

【聚类】K-Means聚类

cluster&#xff1a;簇 原理&#xff1a; 这边暂时没有时间具体介绍kmeans聚类的原理。简单来说&#xff0c;就是首先初始化k个簇心&#xff1b;然后计算所有点到簇心的欧式距离&#xff0c;对一个点来说&#xff0c;距离最短就属于那个簇&#xff1b;然后更新不同簇的簇心&a…

[Linux]文件系统

[Linux]文件系统 文件系统是操作系统的一部分&#xff0c;负责组织、存储和管理存储在外部设备上的文件和目录&#xff0c;也就是操作系统管理外设中的文件的策略。本文讲解的是Ext2文件系统。Linux操作系统使用的就是Ext系列的文件系统。 文章目录 [Linux]文件系统了解磁盘结构…

如何选择合适的HTTP代理服务器

HTTP代理服务器是一种常见的网络代理方式&#xff0c;它可以帮助用户隐藏自己的IP地址&#xff0c;保护个人隐私和安全。然而&#xff0c;选择合适的HTTP代理服务器并不容易&#xff0c;需要考虑多个因素。本文将介绍如何选择合适的HTTP代理服务器。 了解代理服务器的类型 HTT…

Web Components详解-Shadow DOM插槽

前言 插槽实际上也属于组件通信的一种方式&#xff0c;但是由于其强大的api和实用性&#xff0c;我将其单独拆开来介绍。 定义 Slot&#xff08;插槽&#xff09;是Web Components中一个重要的特性&#xff0c;它允许在组件内部定义占位符&#xff0c;以便父组件可以向其中插…

【Java并发】聊聊ReentrantReadWriteLock锁降级和StampedLock邮戳锁

面试题 1.你说你用过读写锁&#xff0c;锁饥饿问题是什么&#xff1f; 2.有没有比读写锁更快的锁&#xff1f; 3.StampedLock知道吗?(邮戳锁/票据锁) 4.ReentrantReadWriteLock有锁降级机制策略你知道吗&#xff1f; 在并发编程领域&#xff0c;有多线程进行提升整体性能&…

流程图 and/or/xor 讲解

and表示后续2个活动同时触发&#xff0c; or表示后续2个活动可触发其中的1个或2个&#xff0c;无排他性&#xff0c;也就是每个活动的触发不影响其他活动&#xff1b; xor表示后续2个活动只触发一个&#xff0c;有排他性&#xff0c;也就是只能触发其中一个。 示例演示“OR”…

云原生Kubernetes:Yaml文件编写

目录 一、理论 1.Kubernetes与yaml文件 二、实验 1.Kubernetes与yaml文件 三、问题 1.kubectl create 和 kubectl apply区别 四、总结 一、理论 1.Kubernetes与yaml文件 &#xff08;1&#xff09;Kubernetes支持管理资源对象的文件格式 Kubernetes支持YAML 和JSON 格…

基于微信小程序的智能垃圾分类回收系统,附源码、教程

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 1 简介 视频演示地址&#xff1a; 基于微信小程序的智能垃圾分类回收系统&#xff0c;可作为毕业设计 小…

PDF文件太大怎么办?三招教会你PDF文件压缩

PDF文件太大怎么办&#xff1f;这是许多人在处理PDF文件时遇到的问题。为了帮助大家解决这个问题&#xff0c;下面总结了三个可以解决PDF文件过大问题的方法&#xff0c;需要的朋友抓紧来看看吧~ 方法一&#xff1a;使用嗨格式压缩大师 嗨格式压缩大师是一款功能强大的PDF压缩…

无涯教程-JavaScript - IMSECH函数

描述 IMSECH函数以x yi或x yj文本格式返回复数的双曲正割。复数的双曲正割被定义为双曲余弦的倒数,即 六(z) 1/cosh(z) 语法 IMSECH (inumber)争论 Argument描述Required/OptionalInumberA complex number for which you want the hyperbolic secant.Required Notes Ex…

手机usb连接电脑上网怎么做?掌握2个方法即可!

“我的电脑不知道怎么就连不上网络了&#xff0c;之前好像听说可以使用手机usb连接网络上网&#xff0c;但是不知道具体应该怎么操作。有没有知道详细操作步骤的朋友可以分享一下呀&#xff01;” 在需要临时共享手机网络连接或电脑无法连接Wi-Fi的情况下&#xff0c;将手机通过…

【C++基础】实现日期类

​&#x1f47b;内容专栏&#xff1a; C/C编程 &#x1f428;本文概括&#xff1a; C实现日期类。 &#x1f43c;本文作者&#xff1a; 阿四啊 &#x1f438;发布时间&#xff1a;2023.9.7 对于类的成员函数的声明和定义&#xff0c;我们在类和对象上讲到过&#xff0c;需要进行…

c++通过tensorRT调用模型进行推理

模型来源&#xff1a; 算法工程师训练得到的onnx模型 c对模型的转换&#xff1a; 拿到onnx模型后&#xff0c;通过tensorRT将onnx模型转换为对应的engine模型&#xff0c;注意&#xff1a;训练用的tensorRT版本和c调用的tensorRT版本必须一致。 如何转换&#xff1a; 算法工…

2020年12月 C/C++(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C++编程(1~8级)全部真题・点这里 第1题:数组指定部分逆序重放 将一个数组中的前k项按逆序重新存放。例如,将数组8,6,5,4,1前3项逆序重放得到5,6,8,4,1。 时间限制:1000 内存限制:65536 输入 输入为两行: 第一行两个整数,以空格分隔,分别为数组元素的个数n(1 < n…

在Qt5中SQLite3的使用

一、SQLite简要介绍 什么是SQLite SQLite是一个进程内的库&#xff0c;实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库&#xff0c;这意味着与其他数据库不一样&#xff0c;您不需要在系统中配置。 就像其他数据库&#xff0c;S…

基于javaweb的CT图像管理系统(servlet+jsp)

系统简介 本项目采用eclipse工具开发&#xff0c;jspservletjquery技术编写&#xff0c;数据库采用的是mysql&#xff0c;navicat开发工具。 三个角色&#xff1a;管理员&#xff0c;普通用户&#xff0c;医生 模块简介 管理员&#xff1a; 1、登录 2、用户管理 3、医生管…

ARM DIY(十)LRADC 按键

前言 ARM SOC 有别于单片机 MCU 的一点就是&#xff0c;ARM SOC 的 GPIO 比较少&#xff0c;基本上引脚都有专用的功能&#xff0c;因为它很少去接矩阵键盘、众多继电器、众多 LED。 但有时 ARM SOC 又需要三五个按键&#xff0c;这时候 LRADC 就是一个不错的选择&#xff0c;…

C# OpenVino Yolov8 Detect 目标检测

效果 项目 代码 using OpenCvSharp; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using static System.Net.Mime.MediaT…

python趣味编程-数独游戏

数独游戏是一个用Python编程语言编写的应用程序。该项目包含可以显示实际应用程序的基本功能。该项目可以让修读 IT 相关课程并希望开发简单应用程序的学生受益。这个Python 数独游戏是一个简单的项目,可用于学习tkinter库的实践。这个数独游戏可以提供Python编程的基本编码技…

黑马JVM总结(三)

&#xff08;1&#xff09;栈内存溢出 方法的递归调用&#xff0c;没有设置正确的结束条件&#xff0c;栈会有用完的一天&#xff0c;导致栈内存溢出 可以修改栈的大小&#xff1a; 再次运行&#xff1a;减少了次数 案例二&#xff1a; 两个类的循环应用问题&#xff0c;导致Js…