快速排序(java细节实现)

目录

快速排序:

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.mfbz.cn/a/604599.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++:AVL树

概念&#xff1a; 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查 找元素相当于在顺序表中搜索元素&#xff0c;效率低下。 如图所示&#xff0c;搜索二叉树不能面对右边的树&#xff0c;这种极端的情况&#xf…

[iOS]从拾遗到Runtime(上)

[iOS]从拾遗到Runtime(上) 文章目录 [iOS]从拾遗到Runtime(上)写在前面名词介绍instance 实例对象class 类对象meta-class 元类对象为什么要有元类&#xff1f; runtimeMethod(objc_method)SEL(objc_selector)IMP 类缓存(objc_cache)Category(objc_category) 消息传递消息传递的…

【how2j JQuery部分】课后题答案及相关笔记

练习题 <script src"jquery.min.js"></script><script>$(function(){$(tr:odd).css({"background-color":"#f8f8f8"});}); </script> <style> table{border-collapse:collapse;width:90%;} tr{border-bottom-sty…

安捷伦E4991A美国原装二手KEYSIGHT、E4990A阻抗分析仪

商品品牌&#xff1a;安捷伦Agilent/是德KEYSIGHT 商品型号&#xff1a;E4990A 商品价格&#xff1a;面议或电议 商品详情&#xff1a; Agilent E4990A阻抗分析仪&#xff0c;20 Hz 至 10/20/30/50/120 MHz 主要特性与技术指标 5 种频率选件&#xff1b;20 Hz 至 10/20/30/50/1…

C++学习————第十天(string的基本使用)

1、string 对象类的常见构造 (constructor)函数名称 功能说明&#xff1a; string() &#xff08;重点&#xff09; 构造空的string类对象&#xff0c;即空字符串 string(const char* s) &#xff08;重点&#xff09;…

Java_从入门到JavaEE_11

一、抽象类及抽象方法 1.认识抽象类及抽象方法 应用场景&#xff1a;当一个方法必须在父类中出现&#xff0c;但是这个方法又不好实现&#xff0c;就把该方法变成抽象方法&#xff0c;交给非抽象的子类去实现 实例&#xff1a; //抽象类 public abstract class 类名{//抽象方…

Ansible----playbook模块之templates模块、tags模块、roles模块

目录 引言 一、templates模块 &#xff08;一&#xff09;关键信息 &#xff08;二&#xff09;实际操作 1.定义主机组 2.设置免密登录 3.分别建立访问目录 4.定义模板文件 5.创建playbook文件 6.执行剧本 7.验证结果 二、tags模块 &#xff08;一&#xff09;创建…

stm32_RTC_2_HAL——stm32CudeMX

介绍 RTC&#xff08;实时时钟&#xff09;不仅仅提供计数功能&#xff0c;它是一个完整的时钟和日历模块&#xff0c;用于提供日期和时间信息。RTC 能够提供年、月、日、星期、时、分、秒等时间信息&#xff0c;并且通常具有闹钟功能&#xff0c;可以用于定时唤醒或触发事件。…

Qt | QLineEdit 类(行编辑器)

01、上节回顾 Qt | QComboBox(组合框)02、QLineEdit 1、QLineEdit 类是 QWidget 类的直接子类,该类实现了一个单行的 输入部件,即行编辑器,见右图 2、验证器(QValidator 类)和输入掩码简介:主要作用是验证用户输入的字符是否符合验证器 的要求,即限制对用户的输入,比…

详细介绍ARM-ORACLE Database 19c数据库下载

目录 1. 前言 2. 获取方式 2.1 ORACLE专栏 2.2 ORACLE下载站点 1. 前言 现有网络上已有非常多关于ORACLE数据库机下载的介绍&#xff0c;但对于ARM平台的介绍不多&#xff0c;借此机会我将该版的下载步骤做如下说明&#xff0c;希望能够一些不明之人提供帮助和参考 2. 获…

【STM32G474】利用Cpp编写STM32代码后,Cubemx修改配置后代码报错147个error,如何处理?

问题描述 打开Cubemx&#xff0c;添加TIM7用于定时器精准延时&#xff0c;生成代码后&#xff0c;Keil提示有147个error。 之前是Cubemx是没有问题的&#xff0c;是利用Cpp编写stm32&#xff08;将Keil改为Version6&#xff09;后才导致Cubemx配置失败&#xff1a; debug成功…

[学习笔记]CyberDog小米机器狗 开发学习

1、机器狗本身是UbuntuROS2系统 2、控制机器人只需要了解lcm和Ros topic通讯 3、传感器数据&#xff08;包括一些imu(/imu)、激光雷达(/scan)&#xff09;会进行topic的一个广播。 仿真环境通信接口&#xff1a; -命令输入(见后续运控说明) 运控lcm数据接口 Motion man…

Gmail邮箱怎么注册?2024年完整指南(包含跳过手机号验证)

一、为什么要注册Gmail邮箱&#xff1f; 全球通用性&#xff1a;Gmail是一个全球性的邮件服务平台&#xff0c;被广泛认可和信赖。因为客户对于Gmail的接受度高&#xff0c;无需担心邮件被自动标记为垃圾邮件。 整合营销工具&#xff1a;通过Gmail账号&#xff0c;你可以轻松…

CleanMyMac X 4.15.3 版本发布

CleanMyMac X 4.15.3 版本发布&#xff0c;一款苹果 macOS 系统好用的伴侣软件&#xff0c;其包含 1.一键深度清理。2.系统垃圾专清。3.大/旧文件专清。4.系统提速。5.性能悬浮窗。6.恶意软件防护。7.隐私保护。8.软件卸载器。9.软件更新器等 9 大功能&#xff0c;为您的苹果电…

Flask-HTTP请求、响应、上下文、进阶实验

本节主要目录如下&#xff1a; 一、请求响应循环 二、HTTP请求 2.1、请求报文 2.2、Request对象 2.3、在Flask中处理请求 2.4、请求钩子 三、HTTP响应 3.1、响应报文 3.2、在Flask中生成响应 3.3、响应格式 3.4、Cookie 3.5、session&#xff1a;安全的Cookie 四、…

[公开课学习]台大李宏毅-自注意力机制 Transformer

自注意力机制 存在一些问题&#xff0c;将vector set/sequence作为input&#xff0c;例如&#xff1a; 文字处理&#xff1a;将文字用one-hot表示&#xff0c;或者向量空间的向量表示&#xff0c;然后进行翻译任务等语音处理&#xff1a;25ms音频作为一个向量&#xff0c;10m…

初识C++ · 模板初阶

目录 1 泛型编程 2 函数模板 3 类模板 1 泛型编程 模板是泛型编程的基础&#xff0c;泛型我们碰到过多次了&#xff0c;比如malloc函数返回的就是泛型指针&#xff0c;需要我们强转。 既然是泛型编程&#xff0c;也就是说我们可以通过一个样例来解决类似的问题&#xff0c…

pytorch基础: torch.unbind()

1. torch.unbind 作用 说明&#xff1a;移除指定维后&#xff0c;返回一个元组&#xff0c;包含了沿着指定维切片后的各个切片。 参数&#xff1a; tensor(Tensor) – 输入张量dim(int) – 删除的维度 2. 案例 案例1 x torch.rand(1,80,3,360,360)y x.unbind(dim2)print(&…

gitlab集群高可用架构拆分部署

目录 前言 负载均衡器准备 外部负载均衡器 内部负载均衡器 (可选)Consul服务 Postgresql拆分 1.准备postgresql集群 手动安装postgresql插件 2./etc/gitlab/gitlab.rb配置 3.生效配置文件 Redis拆分 1./etc/gitlab/gitlab.rb配置 2.生效配置文件 Gitaly拆分 1.…

BACnet转MQTT网关智联楼宇json格式自定义

智能建筑的BACnet协议作为楼宇自动化领域的通用语言&#xff0c;正逐步迈向更广阔的物联网世界。随着云计算和大数据技术的飞速发展&#xff0c;如何将BACnet设备无缝融入云端生态系统&#xff0c;成为众多楼宇管理者关注的焦点。本文将以一个实际案例&#xff0c;揭示BACnet网…
最新文章