快速排序的实现和优化~

news2025/1/14 13:34:10

相比于冒泡排序的改进点:

在前面学过的冒泡排序中,由于扫描过程只对相邻的两个元素进行比较,因此在互换两个相邻元素时,只能消除一个逆序,如果能通过两个(不相邻的)元素的交换,消除待排序记录 中的多个逆序,则会大大加快排序的速度,快速排序方法中的一次交换可能消除多个逆序

文字描述:

a.每一轮排序选择一个基准点(pivot)进行分区:

1:让小于基准点的元素进入一个分区,大于基准点的元素进入另一个分区
2:当分区完成后,基准点元素的位置就是其最终位置

b.在子分区内重复以上过程,直至子分区元素个数少于等于1,这体现的是分而治之的思想(divide-and-conquer)

快速排序的实现方式:

单边循环快排(lomuto洛穆托分区方案)

1:选择最右元素作为基准点元素
2:j指针负责找到比基准点小的元素,一旦找到则与i进行交换
3:i指针维护小于基准点元素的边界,也是每次交换的目标索引
4:最后基准点与i交换,i即为分区位置

一次快速排序代码的实现如下:

package bin_find;
import java.util.Arrays;

public class quick_sort {
    public static void main(String[] args) {
        int[] a={5,3,7,2,1,9,8,4};
        quick(a,0,a.length-1);
    }
    public static int quick(int []a,int l,int h){
        int pv=a[h];//基准点元素
        //i相当于是一个分割线,位于它左边的所有元素都是小于基准元素的,位于它右边的元素都是大于基准元素的
        int i=l;
        //j变量根据索引从左往右一旦找到比当前基准元素小的元素,则与索引值为i的元素进行交换
        for(int j=l;j<h;j++){
            if(a[j]<pv){
                swap(a,i,j);
                //交换完成,i++  --->下一个需要排序的元素
                i++;
            }
        }
        //基准点和i的元素进行交换,一轮分区完成
        swap(a,h,i);
        System.out.println(Arrays.toString(a));
        return 0;
    }
    public static void swap(int arr[],int i,int j){
        int t=arr[i];
        arr[i]=arr[j];
        arr[j]=t;
    }
}

输出如下:

[3, 2, 1, 4, 7, 9, 8, 5]

一轮排序的步骤如下:

在这里插入图片描述

一轮排序图解如下所示:

pv:表示基准点元素

第一次:由于5比4大,所以不发生交换,j继续向后移动寻找比4小的元素

在这里插入图片描述
在这里插入图片描述

第二次:

j移动到元素3的位置,3比4小,发生交换,i++,结果如下所示:

在这里插入图片描述

在这里插入图片描述

第3次:当j移动到元素2的位置,2比4小,所以发生交换,且i++

在这里插入图片描述

j再移动直到找到1,元素1小于4,将i和j指向的元素进行交换,如下所示:

在这里插入图片描述

此时j已经移动到了基准点前一个位置:

在这里插入图片描述

最后只需要将i和pv指向的元素进行交换,则一轮快排结束

在这里插入图片描述

通过一轮快排结束的结果我们会发现,以i指向的元素作为基准,位于它左边的元素都是小于它的,位于它右边的元素都是大于它的

完整快速排序代码如下:

package bin_find;
import java.util.Arrays;

public class quick_sort {
    public static void main(String[] args) {
        int[] a={5,3,7,2,1,9,8,4};
        recurrence(a,0,a.length-1);
    }

    //该方法的作用是:递归实现分区排序直至整个数组有序,通过每轮排序返回的下标值进而确定这次排序的范围
    public static void recurrence(int []a,int l,int h){
        if(l>=h){//递归结束的条件:当区间只有一个元素或者没有元素时,有等号的原因是由于当l恰好等于返回的索引值,此时p再减1就使得h<l
            return;
        }
        int p=quick(a,l,h);//基准点的索引值
        recurrence(a,l,p-1);//左边分区范围:第一个元素~基准元素的前一个元素
        recurrence(a,p+1,h);//右边分区范围:基准元素的下一个元素~最后一个元素
    }
    public static int quick(int []a,int l,int h){
        int pv=a[h];
        int i=l;
        for(int j=l;j<h;j++){
            if(a[j]<pv){
                //优化1:
                if(i!=j) {//当j找到小于基准点的值和i指向的元素不是同一个元素时,发生交换,这样能够减少不必要的交换次数
                    swap(a, i, j);
                }
                i++;
            }
        }
        //优化2:
        if(i!=h){//当基准点元素和i指向的元素不是同一个元素时,发生交换,这样能够减少不必要的交换次数
        swap(a,h,i);
        }
        System.out.println(Arrays.toString(a));
        return i;
    }
    public static void swap(int arr[],int i,int j){
        int t=arr[i];
        arr[i]=arr[j];
        arr[j]=t;
    }
}

输出:

[3, 2, 1, 4, 7, 9, 8, 5]
[1, 2, 3, 4, 7, 9, 8, 5]
[1, 2, 3, 4, 7, 9, 8, 5]
[1, 2, 3, 4, 5, 9, 8, 7]
[1, 2, 3, 4, 5, 7, 8, 9]
[1, 2, 3, 4, 5, 7, 8, 9]

双边循环快排(并不完全等价于hoare霍尔分区方案)

1:选择最左元素作为基准点元素
2:j指针负责从右向左找比基准点小的元素,i指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至i,j相交
3:最后基准点与i(此时i与j相等)交换,i即为分区位置

一轮双边循环快排图解如下:

在这里插入图片描述

j此时指向的元素为4比5小,因此j不需要移动了,此时需要等待i移动直至找到大于基准元素的元素:

在这里插入图片描述

此时i找到了大于基准元素的元素,i和j指向的元素进行交换:

在这里插入图片描述

j移动找到1,小于基准元素,等待i找到大于基准元素的元素9时:

在这里插入图片描述

二者发生交换:

当下次再开始寻找时,j寻找小于基准元素的值,找到元素1,i和j相等,如下所示:

在这里插入图片描述

最后只需要交换基准元素和i,j指向的同一个元素即可,表示本轮排序结束:

在这里插入图片描述

代码如下所示:

package bin_find;
import java.util.Arrays;

public class quick_sort {
    public static void main(String[] args) {
        int[] a={5,3,7,2,9,8,1,4};
        recurrence(a,0,a.length-1);
    }
    public static void recurrence(int []a,int l,int h){
        if(l>=h){//递归结束的条件:当区间只有一个元素或者没有元素时,有等号的原因是由于当l恰好等于返回的索引值,此时p再减1就使得h<l
            return;
        }
        int p=quick(a,l,h);//基准点的索引值
        recurrence(a,l,p-1);//左边分区范围:第一个元素~基准元素的前一个元素
        recurrence(a,p+1,h);//右边分区范围:基准元素的下一个元素~最后一个元素
    }

    //该方法的作用是:递归实现分区排序直至整个数组有序,通过每轮排序返回的下标值进而确定这次排序的范围
    public static int quick(int []a,int l,int h){
        int pv=a[l];
        int i=l;
        int j=h;
        while(i<j){
            while (i<j&&a[j]>pv){
                j--;
            }
            while(i<j&&a[i]<=pv){
                i++;
            }
            swap(a,i,j);
        }
        swap(a,l,i);//i和j都可以,因为他们指向同一个元素
        System.out.println(Arrays.toString(a));
        return i;
    }
    public static void swap(int arr[],int i,int j){
        int t=arr[i];
        arr[i]=arr[j];
        arr[j]=t;
    }
}

输出:

[1, 3, 4, 2, 5, 8, 9, 7]
[1, 3, 4, 2, 5, 8, 9, 7]
[1, 2, 3, 4, 5, 8, 9, 7]
[1, 2, 3, 4, 5, 7, 8, 9]

针对上述的算法,有以下三个地方需要注意:

细节一:

在这里插入图片描述

答案:不可以发生颠倒

在开始进行排序的过程中,两个while循环位置发生颠倒看似没有什么问题,但经过几轮之后,最终会出现下述情况导致整个逻辑混乱

在这里插入图片描述

细节二:

在这里插入图片描述

答案:不可以

原因如下:假设我们现在的条件为a[i]<pv,j开始找比基准点小的元素,找到4后,i开始找比基准点大的元素,此a[i]<pv不满足,i++不会被执行,i依然指向基准点元素,第二个while循环退出执行swap函数,导致基准点元素被调换,因此加等号条件的作用是,为了在最开始跳过基准点元素,避免不必要的逻辑错误

在这里插入图片描述

细节三:

在这里插入图片描述

答案:不可以

原因如下:

假设此时,我们将内层的两个循环中的i<j条件同时去除,那么就会发生如下所示,当j从右往左找到了小于基准点的元素停下,i从基准点出发寻找大于基准点的元素,当i++到元素3的位置,逻辑上应为排序完成,但由于没有i<j的条件,因此i会++到元素6的位置,最终将元素6换到了左侧,这显然已经违背正确的结果了

在这里插入图片描述

双边循环需要注意的点:

1:基准点在左边并且要先j后i
2while(i<j&&a[j]>pv)j--
3:while(i<j&&a[i]<=pv)i++

快速排序的特点:

平均时间复杂度是O(nlog2^n),最坏时间复杂度是O(n ^n)

数据量较大时,优势非常明显

属于不稳定的排序

举例;

3,3,2

采用单边循环快排,2作为基准元素,起初i,j都指向3,从左往右找比2小的元素,当找到3退出循环,由于i指向3,基准元素为2,二者并不相等,因此发生交换,这就导致3和3的前后关系发生变化,因此快速排序属于不稳定排序

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

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

相关文章

docker desktop window10家庭版踩坑实录

安装 桌面版&#xff1a;https://www.docker.com/products/docker-desktop 这里我就安装的是桌面版 选择windows 前置工作 1.按下 wins&#xff08;找到这个&#xff09; 将下面的这个勾选中&#xff0c;如果你是家庭版很可能没有这个东西&#xff0c;那么请看我的这篇文章…

5-5中央处理器-指令流水线

文章目录一.基本概念1.多条指令在处理器中的执行方式&#xff08;1&#xff09;顺序执行方式/串行执行方式&#xff08;2&#xff09;流水线执行方式①一次重叠执行方式②二次重叠执行方式2.流水线的表示方法&#xff08;时空图&#xff09;3.超标量流水线二.分类1.部件功能级、…

第四十五章 动态规划——背包问题模型(二)

一、概述 我们在上一章中已经对背包模型做了一定地讲解&#xff0c;但是我们发现其实在上一章节中我们所介绍的例题大部分是给背包问题套上一个背景&#xff0c;当我们识破了背后的模型后&#xff0c;我们就可以直接套用模板&#xff0c;基本不需要对代码做改变。 那么在这一…

SpringBoot读写Redis客户端并实现技术切换(Jedis)

SpringBoot整合Redishttps://blog.csdn.net/weixin_51882166/article/details/128759780?spm1001.2014.3001.5501 读写客户端 首先应该打开redis服务&#xff1b; cd命令进入Redis安装目录下&#xff1a; 进入Redis客户端&#xff1a; redis-cli.exe -h 127.0.0.1 -p 6379…

梯度下降算法有哪些?有什么区别?【背景、原理、公式、代码】

一、梯度下降算法背景 梯度下降是迭代法的一种,可以用于求解最小二乘问题(线性和非线性都可以)。在求解机器学习算法的模型参数,即无约束优化问题时,梯度下降(Gradient Descent)是最常采用的方法之一,另一种常用的方法是最小二乘法。在求解损失函数的最小值时,可以通过梯…

行为型模式-职责链模式

1.概述 在现实生活中&#xff0c;常常会出现这样的事例&#xff1a;一个请求有多个对象可以处理&#xff0c;但每个对象的处理条件或权限不同。例如&#xff0c;公司员工请假&#xff0c;可批假的领导有部门负责人、副总经理、总经理等&#xff0c;但每个领导能批准的天数不同…

记一段相亲反思

记一段相亲反思项目系统启动项目相亲需求的细分高净值人群特定类型的高预期结婚结婚前的彩礼引流系统启动流量&#xff0c;从哪里来&#xff1f;作弊避险&#xff0c;什么钱不能赚&#xff1f;这不是一篇找对象的文章&#xff0c;而是帮别人找对象来赚钱的文章。 项目系统 启…

洛谷-P2114 [NOI2014] 起床困难综合症

题目链接: P2114 [NOI2014] 起床困难综合症 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述: 21 世纪&#xff0c;许多人得了一种奇怪的病&#xff1a;起床困难综合症&#xff0c;其临床表现为&#xff1a;起床难&#xff0c;起床后精神不佳。作为一名青春阳光好少…

Bayesian Personalized Ranking from Implicit Feedback 阅读笔记

Abstract BPR主要用于基于隐式反馈(implicit feedback)的Item Recommendation。 尽管有很多做同样事情的算法比如matrix factorization&#xff0c; knearest-neighbor。但他们并不是直接对于物品排名本身进行预测的。 而BPR则是通过贝叶斯分析得到最大的后验估计量来预测排…

基于蜣螂算法优化概率神经网络PNN的分类预测-附代码

基于蜣螂算法优化概率神经网络PNN的分类预测 - 附代码 文章目录基于蜣螂算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立3.基于蜣螂优化的PNN网络5.测试结果6.参考文献7.Matlab代码摘要&#xff1a;针对PNN神经网络的光滑因…

[GYCTF2020]EasyThinking (ThinkPHP V6.0.0)

[GYCTF2020]EasyThinking 打开以后就注册一些功能&#xff0c;注册admin admin&#xff0c;成功然后尝试search这个方法是否有任意文件读取漏洞&#xff0c;试了试没有任何的回显。 然后个人中心&#xff0c;显示的是自己的历史命令 接下来&#xff0c;呃呃呃就没思路了&…

DFS(五)N皇后

51. N 皇后 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方案…

★ 我的世界各类奇葩武器实现!(命令方块1.13+)

新版execute一出很多玩家都不会了。开头先给大家说一下怎么以旧换新&#xff1a; e.g. 旧版&#xff1a; /execute e[typearrow] ~ ~ ~ summon tnt 新版就改为&#xff1a; /execute at e[typearrow] run summon tnt 非常简单&#xff01; 注记 以下代码块里的命令未经表明一…

Mysql入门技能树-数据类型

数值的隐式类型转换 Joe 需要使用下列表做一项数值计算 create table points(id int primary key auto_increment,x int,y int );计算查询为&#xff1a; select id, (x^2 y^2)/2 as result from points; 得到的结果集中&#xff0c;result 列的类型应该是&#xff1a; 答…

剑指Offer 第1天 第2天

第 1 天 栈与队列&#xff08;简单&#xff09; 剑指 Offer 09. 用两个栈实现队列 class CQueue { public: CQueue() {} void appendTail(int value) { s1.push(value); } int deleteHead() { while(!s1.empty()) { …

AtCoder Regular Contest 154 C. Roller(思维题)

题目 T(T<5e3)组样例&#xff0c;每次给定一个数n(n<5e3)&#xff0c; 和长为n的两个数组a,b&#xff0c;你可以执行以下操作任意次&#xff1a; 操作&#xff1a;选择一个下标i(1<i<n)&#xff0c;将替换为&#xff0c;其中被认为是 问若干次操作后&#xff0…

JUC面试(十)——线程池Callable接口

线程池&Callable接口 前言 获取多线程的方法&#xff0c;我们都知道有三种&#xff0c;还有一种是实现Callable接口 实现Runnable接口实现Callable接口实例化Thread类使用线程池获取 Callable接口 这是一个函数式接口&#xff0c;因此可以用作lambda表达式或方法引用的…

JDBC连接池多线程通过CountDownLatch实现线程并发执行

目录 1.连接池 1.1 什么是连接池 1.2 为什么使用连接池 1.3 连接池的工作原理 1.4我们如何手写【难点】 1.4.1编写说明 1.4.2 创建jdbc.properties 1.4.3 封装一个连接类 1.4.4 创建一个连接池接口 1.4.5 创建一个连接池实现类以及对应方法 1.4.6 创建一个连接池的维…

spingboot如何接受前端请求

首先我们是否用的是rest风格开发的的都是适用的.普通参数get 请求发送方注:由于是get请求不用body(json)接收.接受方post请求发送端注意:在请求体(body)里面用x-www-from-urlencoded(不仅可以发请求,还可以发文件)接受体没有5种不同参数类型的传递普通参数[简单数据]&#xff1…

用 Java 实现计算器功能

练习一 1.设计一个类模拟一个计算器 达到什么需求&#xff1f;加减乘除 需要设计一个方法一个计算方法 控制台输出 首先请输入第一个数 例如数字 1 请输入符号 例如 请输入第二个数 例如 2 第二次 数字 3 请输入符号 - 请输入第二个数 2 结果 1 程序解析&#…