排序算法:快速排序

排序算法:快速排序概述手写排序算法几乎是程序员面试必问的题目,大多数人都会选择写冒泡排序,如果此时你写的是其他改进过的排序算法,相信会让面试官眼前一亮。本文将介绍常见的排序算法中的“快速排序”。基本思想快速排序(QuickSort)是对冒泡排序的一种改进。快速排序由C.A.R.Hoare在1962年提出。它的基本思想是:从要排序的数据中取一个数为“基准数”。 通过一趟排序将要排序的数据…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

微信搜索【程序员囧辉】,关注这个坚持分享技术干货的程序员。

我的最新文章:BAT 老兵的经验之谈,成长路上这个道理越早知道越好

概述

手写排序算法几乎是程序员面试必问的题目,大多数人都会选择写冒泡排序,如果此时你写的是其他改进过的排序算法,相信会让面试官眼前一亮。本文将介绍常见的排序算法中的“快速排序”。

 

基本思想

快速排序(QuickSort)是对冒泡排序的一种改进。快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:

  1. 从要排序的数据中取一个数为“基准数”。
  2. 通过一趟排序将要排序的数据分割成独立的两部分,其中左边的数据都比“基准数”小,右边的数据都比“基准数”大。
  3. 然后再按步骤2对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

该思想可以概括为:挖坑填数 + 分治法。

 

分治法

分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。在计算机科学中,分治法就是运用分治思想的一种很重要的算法。分治法是很多高效算法的基础,如快速排序,归并排序,傅立叶变换(快速傅立叶变换)等等。

 

例子

下面通过一个例子来看看快速排序是怎么工作的,例子中表格中红色的字体为需要填的坑,绿色的字体为已经移动过的数据。

排序算法:快速排序

1)刚开始,i 和 j 分别指向数组头和数组尾,即 i = 0,j = 9,基准数取第一个数,即 index = array[i] = array[0] = 23。

此时,array[0] 的值已经存在了index,因此 array[0] 的位置就好像被挖了个坑,可以填充一个数。

因此,我们从位置 j 开始向左寻找比 index 小的数,当 j = 8 时,符合条件,于是我们将 array[8] 的值填到 array[0] ,即将 9 填入 array[0],并将 i 向右移动一个位置,即 i++。

从位置 j 向左寻找比 index 小的数,并在寻找到后填入坑中,用代码表示如下。

while (i < j && array[j] >= index) { // 向左寻找第一个小于index的数
    j--;
}
if (i < j) {
    array[i++] = array[j]; // 将array[j]填入array[i],并将i向右移动
}

此时,array 数组如下图。

排序算法:快速排序

2)因为 array[0] 的坑被 array[8] 填了,于是 array[8] 的位置又成了一个新的坑。此时我们从位置 i 开始向右寻找比 index 大的数,当 i = 2 时符合条件,于是我们将 array[2] 的值填到 array[8] ,即将 37 填入 array[8],并将 j 向左移动一个位置,即 j–。

从位置 i 向右寻找比 index 大的数,并在寻找到后填入坑中,用代码表示如下(跟上面相似)。

while (i < j && array[i] < index) {// 向右寻找第一个大于index的数
    i++;
}
if (i < j) {
    array[j--] = array[i]; // 将array[i]填入array[j],并将j向左移动
}

此时,array 数组如下图。

排序算法:快速排序

以之后重复上述过程即可。

3)同样的,array[8] 的坑被 array[2] 填了,于是 array[2] 的位置又成了一个新的坑。此时我们从位置 j 开始向左寻找比 index 小的数,当 j = 5 时符合条件,于是我们将 array[5] 的值填到 array[2] ,即将 21 填入 array[2],并将 i 向右移动一个位置,即 i++,此时 array 数组如下图。

排序算法:快速排序

4)同样的,array[2] 的坑被 array[5] 填了,于是 array[5] 的位置又成了一个新的坑。此时我们从位置 i 开始向右寻找比 index 大的数,当 i = 3 时符合条件,于是我们将 array[3] 的值填到 array[5] ,即将 89 填入 array[5],并将 j 向左移动一个位置,即 j–,此时 array 数组如下图。

排序算法:快速排序

5)同样的,array[5] 的坑被 array[3] 填了,于是 array[3] 的位置又成了一个新的坑。此时我们从位置 j 开始向左寻找比 index 小的数,当 j = 4 时符合条件,于是我们将 array[4] 的值填到 array[3] ,即将 2 填入 array[3],并将 i 向右移动一个位置,即 i++,此时 array 数组如下图。

排序算法:快速排序

6)此时,我们发现 i = j,结束遍历,并将index填入 array[4],即将 23 填入 array[4],此时 array 数组如下图。此时,array[4] 左边的数据全比 array[4] 小,而 array[4] 右边的数据全比 array[4] 大。

排序算法:快速排序

7)接下去,我们只需要对 array[4] 两边的数据分别在进行上面的操作即可(分治法),如下图。

排序算法:快速排序

分治的代码可以写成如下:

quickSort(array, low, i - 1); // 递归调用,分治
quickSort(array, i + 1, high); // 递归调用,分治

 

整合

根据以上步骤,稍微整理一下,可以得出快速排序的代码如下:

public class QuickSort {
    private static void quickSort(int[] array, int low, int high) {
        if (low >= high) {
            return;
        }
        int i = low, j = high, index = array[i]; // 取最左边的数作为基准数
        while (i < j) {
            while (i < j && array[j] >= index) { // 向左寻找第一个小于index的数
                j--;
            }
            if (i < j) {
                array[i++] = array[j]; // 将array[j]填入array[i],并将i向右移动
            }
            while (i < j && array[i] < index) {// 向右寻找第一个大于index的数
                i++;
            }
            if (i < j) {
                array[j--] = array[i]; // 将array[i]填入array[j],并将j向左移动
            }
        }
        array[i] = index; // 将基准数填入最后的坑
        quickSort(array, low, i - 1); // 递归调用,分治
        quickSort(array, i + 1, high); // 递归调用,分治
    }

    public static void quickSort(int[] array) {
        if (array == null || array.length == 0) {
            return;
        }
        quickSort(array, 0, array.length - 1);
    }
}

 

时间复杂度

最好情况的时间复杂度为O(nlogn),过程比较复杂,就不在此赘述。

最差情况下每一次取到的数(基准数)都是当前要比较的数中的最大/最小值,在这种情况下,每次都只能得到比上一次少1个数的子序列(即要么全比基准数大,要么全比基准小),此时相当于一个冒泡排序,比较的次数 = (n – 1) + (n – 2) + … + 2 + 1 = (n – 1) * n / 2,此时的时间复杂度为:O(n^2)。最差情况一般出现在:待排序的数据本身已经是正序或反序排好了。

 

使用场景

基本上在任何需要排序的场景都可以使用快速排序。虽然快速排序的最坏情况时间复杂度为O(n^2),但是由于基本不会出现,因此可以放心的使用快速排序。在本人的电脑测试,100万的随机数字,快速排序大约耗时120毫秒。

 

最后

理解了快速排序的基本思想后,手写快速排序算法就变得没那么难了,只需要多练习几遍,相信在面试中手写快速排序算法便是小菜一碟。

 

推荐阅读

 

Java 基础高频面试题(2021年最新版)

921天,咸鱼到阿里的修仙之路

两年Java开发工作经验面试总结

4 年 Java 经验,阿里网易拼多多面试总结、心得体会

5 年 Java 经验,字节、美团、快手核心部门面试总结(真题解析)

 

如何写一份让 HR 眼前一亮的简历(附模板)

面试阿里,HashMap 这一篇就够了

面试必问的 MySQL,你懂了吗?

面试必问的线程池,你懂了吗?

跳槽,如何选择一家公司

如何准备好一场大厂面试

MySQL 8.0 MVCC 核心原理解析(核心源码)

复习2个月拿下美团offer,我都做了些啥

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/159459.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 分享S60 精品软件集

    分享S60 精品软件集此贴软件列表中蓝色是中文版,绿色是英文版【管理类】【手机文件管理类】SeleQ.v1.65简体中文完全版文件管理软件System.Explorer.v1.70英文激活成功教程版SELEQ1.70文件管理软件System.Explorer.v1.70中文激活成功教程版SystemExplorerv1.8系统探索器完全汉化安装版NEW读卡器专用的SELEQ安装方法读卡器专用的SmartFileman安装软件文件管

  • ThreadPoolTaskExecutor使用

    ThreadPoolTaskExecutor使用使用场景:多线程

  • C/C++开发人员要了解的几大著名C/C++开源库[通俗易懂]

    C/C++开发人员要了解的几大著名C/C++开源库[通俗易懂]本文详细讲述C/C++开发人员需要了解的几大著名C/C++开源库。

    2022年10月31日
  • 总结测试工程师面试题(含答案)「建议收藏」

    总结测试工程师面试题(含答案)「建议收藏」测试需求分析阶段:阅读需求,理解需求,主要就是对业务的学习,分析需求点,参与需求评审会议。2)、测试计划阶段:主要任务就是编写测试计划,参考软件需求规格说明书,项目总体计划,内容包括测试范围(来自需求文档),进度安排,人力物力的分配,整体测试策略的制定。风险评估与规避措施有一个制定。3)、测试设计阶段:主要是编写测试用例,会参考Prd文档(原型图),概要…

  • Linux—ps -ef|grep详解

    Linux—ps -ef|grep详解【Linux】ps -ef|grep详解Linux下显示系统进程的命令ps,最常用的有ps -ef 和ps aux。这两个到底有什么区别呢?两者没太大差别,讨论这个问题,要追溯到Unix系统中的两种风格,System V风格和BSD 风格,ps aux最初用到Unix Style中,而ps -ef被用在System V Style中,两者输出略有不同。现在的大部分Linux系统都是可以同…

  • dsp28335复位电路_28335串口不能中断

    dsp28335复位电路_28335串口不能中断0前言本期实验目标:采用外部中断方式响应按键触发,实现LED电平反转。外部中断是DSP十分常用的功能,通常用来响应一些控制操作,比如判断按键是否按下,传感器是否接收到信号等等。那么通过该例程,大家则可以快速学会使用外部中断的功能!本节仍然将分为硬件部分、软件部分和实验展示三个方面进行介绍。1硬件部分DSP28335支持XINT1-XINT7和XNMI共8路外部中断源,其中中断源XINT1/2和XNMI可以设定为从GPIO端口A的任意一个管脚输入,即GPIO0-GPIO31。而XINT3/4/5/

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号