快速排序(java细节实现)

news2025/1/10 16:45:43

目录

快速排序:

Hoare版:

挖坑法

快速排序的优化

快速排序的非递归实现

小结


从小到大排序

快速排序:

基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有 元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

简单来说就是给定一个数组:

选取其中的一个数,以这一个数为分界线, 左边都比这个数小, 右边都比这个数大, 然后分为左边和右边的两组再次进行重复操作. 直到这组数只有一个, 听起来麻烦, 实则简单, 重点是找到区间的基准值, 就是找到 作为每一组分界线的那个数.  

将区间按照基准值划分为左右两半部分的常见方式有:Hoare版和挖坑法.

Hoare版:

选取第一个数A作为分界线, 然后右边从后往前进行遍历, 如果这个数B小于A那么停下来, 然后左边从A后便开始遍历, 如果这个数C大于A停下来,   C和A进行交换, 然后重复上述操作, 知道B和C相遇,(B和C指两边正在走的数字), 相遇之后和第一个数字进行交换位置, 得到以交换后的数字为分界线的左右两组数据. 最后在让左右两组数据重复以上操作.

代码实现:

    /**
     * 快速排序
     * 时间复杂度: 最好: O(N*logN)
     *            最坏:O(N^2)
     * 空间复杂度:最好:O(logN)
     *           最坏:O(N)
     * @param array
     */
    public static void quickSort(int[] array) {
        quick(array, 0, array.length-1);
    }
    private static void quick(int[] array, int start, int end) {
        if (start > end) {
            return;
        }
        int pivot = partitionHoare(array, start, end);
        quick(array, start, pivot-1);
        quick(array, pivot+1, end);
    }

    private static int partitionHoare(int[] array, int left, int right) {
        int tmp = array[left];
        int i = left;
        while(left < right) {
            while(left < right && array[right] >= tmp) {
                right--;
            }
            while(left < right && array[left] <= tmp) {
                left++;
            }
            swap(array, left, right);
        }
        swap(array, left, i);
        return left;
    }
    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

易错点:

1> 判断什么时候停止递归, 当start > end的时候. 这个条件的上一个是: end = start+1;

递归时可能交换也可能不交换, 如图所示用 left 和 right 替换start 和 end , 这次递归之后排序完成之后, start > end, 本次左边的递归结束, 然后开始右边的递归, 最后停止.

2> 在partitionHoare函数中, 在循环条件里面, 需要注意在里面循环也需要增加left < right的条件,假如没有这个条件,那么会出现如下图的情况:  这时候right越来越小, 然后right小于0了, 数组越界报错, 要注意里面循环的条件也要遵循外层循环.  并且left < right 要写在前面

3> 在partitionHoare函数中, 在循环条件里面, 注意为什么当array[i] == tmp时也需要right--或者left++, 因为这样的话才能让循环进行下去, 否则会当值陷入死循环.

举个例子: 如图下图, 如果==跳出循环的话, 6和6进行交换 之后会一直进行这个操作.

4> 为什么需要从右边开始比较移动数据呢. 如果从左边开始会出现什么情况呢?

举个例子:

而我们选择先从右边筛选数据的结果则是正确的.

这就是为什么必须一定要从右边开始进行选择的原因, 就是为了尽量让这个数停在靠左的位置, 把比较大的数放在右边.

挖坑法

原理都差不多, 挖坑法选择了一个数作为基准, 然后挖出这个数A, 然后从左边选择比A小的数B,将B填入坑中, 然后从左边挖出比A大的数C放在右边的坑上,最后当遇到坑后直接将A填入坑中.

        /**
     * 快速排序
     * 时间复杂度: 最好: O(N*logN)
     *            最坏:O(N^2)
     * 空间复杂度:最好:O(logN)
     *           最坏:O(N)
     * @param array
     */
    public static void quickSort(int[] array) {
        quick(array,0,array.length-1);
    }
    private static void quick(int[] array, int start, int end) {
        if(start >= end) {
            return;
        }
        int pivot = partitionHole(array,start,end);
        quick(array, start,pivot-1);
        quick(array, pivot+1,end);
    }
    /**
     * 挖洞快速排序
     * @param array
     * @param left
     * @param right
     * @return
     */

    private static int partitionHole(int[] array, int left, int right) {
        int tmp = array[left];
        while(right>left) {
            //单独的循环不能一直减到超过最左边的边界
            while(right > left && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];
            while(right > left && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;
        return left;
    }
    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

快速排序的优化

接下来讲一下快速排序的优化.为什么需要优化呢, 因为一组数据如果是逆序或者正序的话, 会出现类似于一个树只有一边的情况, 这样会让栈可能挤爆,我们优化目的让递归次数减少的多一点,然后让每一次分割尽量平均一点.

 三数取中法(降低了树的高度)

举个例子:

这个时候我们正常情况下选择1作为key基准值, 最后它的右子树会很高,

现在我们选择这个数组之后找到其中的left, right, mid三个数据中第二小的, 这样的话分为左右两组, 平均了左右树的高度.我们如何找到一个中位数呢.往下看.

我们得到了一组数据的下标, 然后分为几种情况L下标的值大与R下标的值,

我们判断M中间下标的值分为三种情况, 比L大, 或比R小, 或其他(L和R之间).

很容易理解.

代码实现如下:

    /**
     * 快速排序优化
     * 少于一定数时用插入排序
     * 选择更靠中间的key
     */
    private static int middleNum(int[] array, int left, int right)  {
        int mid = (left + right) / 2;
        if(array[left] < array[right]){
            if(array[mid] > array[right]) {
                return right;
            }else if(array[mid] < array[left]) {
                return left;
            }else {
                return mid;
            }
        }else {
            if(array[mid] < array[right]) {
                return right;
            }else if(array[mid] > array[left]) {
                return left;
            }else {
                return mid;
            }
        }
    }

这个实现之后, 当我们进行分组到一定层次后, 可以用直接插入的方法减少递归次数, 当分的组数很多时, 越往下越多, 然后最后几组的数据较少, 用直接插入的方法会在一定程度上减少递归次数, 加快运算速度.

最后代码实现如下:

    /**
     * 快速排序
     * 时间复杂度: 最好: O(N*logN)
     *            最坏:O(N^2)
     * 空间复杂度:最好:O(logN)
     *           最坏:O(N)
     * @param array
     */
    public static void quickSort(int[] array) {
        quick(array,0,array.length-1);
    }
    private static void quick(int[] array, int start, int end) {
        if(start >= end) {
            return;
        }
        if(end - start+1<=15) {
            insertSort(array, start, end);
            return;
        }
        int index = middleNum(array, start, end);
        swap(array, index, start);
        int pivot = partitionHole(array,start,end);
        quick(array, start,pivot-1);
        quick(array, pivot+1,end);
    }
    public static void insertSort(int[] array, int left, int right) {
        for(int i = 1+left; i <= right; i++) {
            int j = i-1;
            int tmp = array[i];
            for (;j>=left;j--) {
                if(tmp < array[j]) {
                    array[j+1] = array[j];
                }else {
                    break;
                }
            }
            array[j+1] =tmp;
        }
    }

    /**
     * Hoare版实现
     * @param array
     * @param left
     * @param right
     * @return
     */
    private static int partitionHoare(int[] array, int left, int right) {
        int tmp = array[left];
        int i = left;
        while(right>left) {
            //单独的循环不能一直减到超过最左边的边界
            while(right > left && array[right] >= tmp) {
                right--;
            }
            while(right > left && array[left] <= tmp) {
                left++;
            }
             swap(array,left,right);
        }
        swap(array, i, left);
        return left;
    }

    /**
     * 挖洞快速排序
     * @param array
     * @param left
     * @param right
     * @return
     */

    private static int partitionHole(int[] array, int left, int right) {
        int tmp = array[left];
        while(right>left) {
            //单独的循环不能一直减到超过最左边的边界
            while(right > left && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];
            while(right > left && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;
        return left;
    }

    /**
     * 快速排序优化
     * 少于一定数时用插入排序
     * 选择更靠中间的key
     */
    private static int middleNum(int[] array, int left, int right)  {
        int mid = (left + right) / 2;
        if(array[left] < array[right]){
            if(array[mid] > array[right]) {
                return right;
            }else if(array[mid] < array[left]) {
                return left;
            }else {
                return mid;
            }
        }else {
            if(array[mid] < array[right]) {
                return right;
            }else if(array[mid] > array[left]) {
                return left;
            }else {
                return mid;
            }
        }
    }

快速排序的非递归实现

给定一组数据, 先第一次找到pivot的值, 然后建立一个栈, 先判断每一边的数是不是大于一个,(如何判断, pivot+1 < e 右边元素大于一个, pivot - 1 > s 左边元素大于一个) ,如果大于一个,  把左边数据的left  和   right放入栈中, 然后再把右边数据的left  和  right放入栈中,反之不放入数据.

然后判断栈空不空, 不空取出两个数r,l 

然后进行第二次调用partition方法来寻找pivot基准值, 然后将每组数据的下标放到栈以此类推.

步骤:

1> 调用partiton方法找到pivot

2> 左边有没有两个元素, 有的话放到栈中

3> 右边同理

4> 如果栈不空的话, 取出r 和 l

代码实现 

    /**
     * 快速排序的非递归实现
     * @param array
     */
    public static void quickSortNor(int[] array) {
        int start = 0;
        int end = array.length-1;
        Stack<Integer> stack = new Stack<>();
        int pivot = partitionHoare(array, start, end);
        if(pivot-1>start) {
            stack.push(start);
            stack.push(pivot-1);
        }
        if(pivot+1<end) {
            stack.push(pivot+1);
            stack.push(end);
        }
        while(!stack.isEmpty()) {
            end = stack.pop();
            start = stack.pop();
            pivot = partitionHoare(array, start, end);
            if(pivot-1>start) {
                stack.push(start);
                stack.push(pivot-1);
            }
            if(pivot+1<end) {
                stack.push(pivot+1);
                stack.push(end);
            }
        }
    }
    /**
     * Hoare版实现
     * @param array
     * @param left
     * @param right
     * @return
     */
    private static int partitionHoare(int[] array, int left, int right) {
        int tmp = array[left];
        int i = left;
        while(right>left) {
            //单独的循环不能一直减到超过最左边的边界
            while(right > left && array[right] >= tmp) {
                right--;
            }
            while(right > left && array[left] <= tmp) {
                left++;
            }
             swap(array,left,right);
        }
        swap(array, i, left);
        return left;
    }

小结

需要手敲代码,代码中的一些易错点需要认真整理理解

如有不足,望指正.

如有疑问,乐意解答. 

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

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

相关文章

二叉树的基础遍历2.0

1.0入口&#xff1a;二叉树的基础遍历-CSDN博客 在1.0中使用的是简单的结构体建树&#xff0c;本文注重用二维vector建树。 前序&#xff0c;中序和后序的分析1.0已给出&#xff0c;本文不做过多介绍&#xff0c;本文重点讲二叉树的层序遍历。 先奉上前中后序的代码&#xf…

只允许内网访问时,如何设置hosts

1、Hosts文件简介 hosts文件是一个没有扩展名的计算机文件&#xff0c;用于将主机名与对应的 IP 地址关联起来。在操作系统中&#xff0c;hosts文件通常用于在本地解析域名&#xff0c;以便将域名映射到特定的IP地址。这个文件可以用来屏蔽广告、加速访问特定网站、解决DNS解析…

计算机网络技术主要学什么内容,有哪些课程

计算机网络技术专业是一个涉及理论与实践紧密结合的学科&#xff0c;主要学习内容有计算机网络基础、网络设备技术、网络编程等内容&#xff0c;以下是上大学网&#xff08;www.sdaxue.com&#xff09;整理的计算机网络技术主要学什么内容&#xff0c;供大家参考&#xff01; 基…

服务运维问题

2024-05-01&#xff08;docker 部署的 jar包自动关闭&#xff09; 查询运行情况&#xff1a;处于退出状态 docker ps -a 查询日志&#xff1a;看不出问题 docker logs -f --tail1000 demo-java 查询关于java服务日志&#xff1a;Out of memory: Kill process 16236 (java) …

学习大数据,所需更要的shell基础(2)

文章目录 read读取控制台输入函数系统函数bashnamedirname 自定义函数Shell工具&#xff08;重点&#xff09;cutawk 正则表达式入门常规匹配常用特殊字符 read读取控制台输入 1&#xff09;基本语法 read (选项) (参数) ①选项&#xff1a; -p&#xff1a;指定读取值时的提示…

使用 Parallels Desktop 在 Mac 上畅玩 PC 游戏

我们不再需要接受 “Mac 不是为游戏而打造” 这一事实&#xff1b;Parallels Desktop 通过将电脑变成高性能的游戏设备&#xff0c;从而改变了一切。 Parallels Desktop 充分利用 Mac 硬件的强大功能&#xff0c;让您无缝畅玩 Windows 专享游戏。 性能得到提升&#xff0c;可玩…

顺序栈的操作

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd;既然选择了远方&#xff0c;当不负青春…

国产PLC海为如何与电脑通信

前言 这几天接触到了国产海为PLC&#xff0c;做一个记录&#xff01;学习一下&#xff01; 串口联机 步骤 1&#xff1a;使用 USB 转 485 线连接 A8&#xff08;RS485 通讯口&#xff09;和电脑&#xff1b; 步骤 2&#xff1a;打开 Haiwell happy PLC 编程软件&#xff0c…

【C++】C++11--- 列表初始化|关键字

目录 前言 列表初始化 创建对象时的列表初始化 单参数隐式类型转换 多参数的隐式类型转换 new表达式中使用列表初始化 列表初始化适用于STL 容器 模板类initializer_list 关键字auto 关键字decltype 关键字nullptr 前言 C标准10年磨一剑&#xff0c;第二个真正意义上…

Windows系统完全卸载删除 Node.js (包含控制面板找不到node.js选项情况)

1.打开cmd命令行窗口&#xff0c;输入npm cache clean --force 回车执行 2.打开控制面板&#xff0c;在控制面板中把Node.js卸载 移除之后检查环境变量是否也移除&#xff1a;点击Path&#xff0c;点击编辑。 把环境变量中和node有关的全部移除&#xff0c;然后点击确定。 3.重…

Linux中云盘/磁盘,爆满处理方式

1&#xff1a;du和df命令查看磁盘大小不一致 以下是阿里云服务器云盘使用率 运行 du -sh / 大小为20g 我的服务器大小为40g 按道理说这个云盘使用率应该是百分之五十 而运行 df -h / 这个命令是跟这个云盘使用率差不多的。 1.1分析原因 我安装了mysql&#xff0c;nginx…

能否直接上手 Qt ?——看完 C++ 课本后怎么做?

在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「Qt的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;如果你已经阅读了 C 课本&#xff0c;但仍然感到…

C++进阶之路:深入理解编程范式,从面向过程到面向对象(类与对象_上篇)

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

QX-mini51单片机学习-----(3)流水灯

目录 1宏定义 2函数的定义 3延时函数 4标准库函数中的循环移位函数 5循环移位函数与左移和右移运算符的区别 6实例 7keil中DeBug的用法 1宏定义 是预处理语句不需要分号 #define uchar unsigned char//此时uchar代替unsigned char typedef是关键字 后面是接分号…

04、Kafka集群安装

03、Kafka 集群安装 1、准备工作 首先准备一台虚拟机&#xff0c;centos7系统&#xff0c;先在一台上配置安装后&#xff0c;最后克隆成多台机器。 1.1 安装JDK &#xff08;1&#xff09;下载JDK&#xff0c;上传到 /root/software 路径 下载地址&#xff1a;https://www…

【中级软件设计师】上午题15-计算机网络

上午题15-计算机网络 1 网络设备2 协议簇3 TCP和UDP4 SMTP和POP35 ARP和RARP6 DHCP&#xff08;Dynamic Host Configuration Protocol&#xff09;7 URL8 浏览器9 IP地址和子网划分10 IPv611 Windows命令12 路由器 1 网络设备 物理层设备&#xff1a;中继器、集线器&#xff0…

【Linux】CAN根据时钟频率、波特率计算采样点详解

1、采样点知识回顾 参考博客:【CAN】知识点:帧类型、数据帧结构、传输速率、位时间、采样点 CAN 采样点是指在一个数据位的传输周期内,接收器实际采样数据的时间点。这个时间点是以百分比来表示的,它决定了在数据位的传输周期中,何时读取数据位的值。 正确设置采样点对…

【吃透Java手写】2-Spring(下)-AOP-事务及传播原理

【吃透Java手写】Spring&#xff08;下&#xff09;AOP-事务及传播原理 6 AOP模拟实现6.1 AOP工作流程6.2 定义dao接口与实现类6.3 初始化后逻辑6.4 原生Spring的方法6.4.1 实现类6.4.2 定义通知类&#xff0c;定义切入点表达式、配置切面6.4.3 在配置类中进行Spring注解包扫描…

纯血鸿蒙APP实战开发——Canvas实现模拟时钟案例

介绍 本示例介绍利用Canvas 和定时器实现模拟时钟场景&#xff0c;该案例多用于用户需要显示自定义模拟时钟的场景。 效果图预览 使用说明 无需任何操作&#xff0c;进入本案例页面后&#xff0c;所见即模拟时钟的展示。 实现思路 本例的的主要实现思路如下&#xff1a; …

HTB Intuition

Intuition User nmap ┌──(kali㉿kali)-[~/…/machine/SeasonV/linux/iClean] └─$ nmap -A 10.129.22.134 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-04-30 05:29 EDT Nmap scan report for 10.129.22.134 Host is up (0.49s latency). Not shown: 998 …