快速排序算法详解(原理,时间复杂度,实现代码)

2023-11-20

快速排序算法详解(原理、实现和时间复杂度)

快速排序是对冒泡排序的一种改进,由 C.A.R.Hoare(Charles Antony Richard Hoare,东尼·霍尔)在 1962 年提出。

快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。

快速排序的原理

排序算法的思想非常简单,在待排序的数列中,我们首先要找一个数字作为基准数(这只是个专用名词)。为了方便,我们一般选择第 1 个数字作为基准数(其实选择第几个并没有关系)。接下来我们需要把这个待排序的数列中小于基准数的元素移动到待排序的数列的左边,把大于基准数的元素移动到待排序的数列的右边。这时,左右两个分区的元素就相对有序了;接着把两个分区的元素分别按照上面两种方法继续对每个分区找出基准数,然后移动,直到各个分区只有一个数时为止。

这是典型的分治思想,即分治法。下面我们对一个实际例子进行算法描述,讲解快速排序的排序步骤。

快速排序详解

以 “6 1 2 7 9 3 4 5 10 8” 的待排序的数列为例进行排序

接下来开始移动元素。怎么移动呢?其实冒泡排序也涉及对元素的移动,但是那样移动起来很累,比如把最后一个元素移动到第 1 个,就需要比较 n-1 次,同时交换 n-1 次,效率很低。其实,只需把第 1 个元素和最后一个元素交换就好了,这种思想是不是在排序时可以借鉴呢?之前说快速排序就是对冒泡排序的一个改进,就是这个原因。

快速排序的操作是这样的:首先从数列的右边开始往左边找,我们设这个下标为 i,也就是进行减减操作(i–),找到第 1 个比基准数小的值,让它与基准值交换;接着从左边开始往右边找,设这个下标为 j,然后执行加加操作(j++),找到第 1 个比基准数大的值,让它与基准值交换;然后继续寻找,直到 i 与 j 相遇时结束,最后基准值所在的位置即 k 的位置,也就是说 k 左边的值均比 k 上的值小,而 k 右边的值都比 k 上的值大。

假如用 “6 1 2 7 9 3 4 5 10 8” 这个初始序列来进行快速排序

分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即=10),指向数字。
在这里插入图片描述
首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为什么)。哨兵j一步一步地向左挪动(即j–),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。
在这里插入图片描述

在这里插入图片描述
现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下:
6 1 2 5 9 3 4 7 10 8
在这里插入图片描述
在这里插入图片描述
到此,第一次交换结束。接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下:

6 1 2 5 4 3 9 7 10 8

第二次交换结束,“探测”继续。哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下:

3 1 2 5 4 6 9 7 10 8
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。

OK,解释完毕。现在基准数6已经归位,它正好处在序列的第6位。此时我们已经将原来的序列,以6为分界点拆分成了两个序列,左边的序列是“3 1 2 5 4”,右边的序列是“9 7 10 8”。接下来还需要分别处理这两个序列。因为6左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理6左边和右边的序列即可。现在先来处理6左边的序列现吧。

左边的序列是“3 1 2 5 4”。请将这个序列以3为基准数进行调整,使得3左边的数都小于等于3,3右边的数都大于等于3。好了开始动笔吧

如果你模拟的没有错,调整完毕之后的序列的顺序应该是:

2 1 3 5 4

OK,现在3已经归位。接下来需要处理3左边的序列“2 1”和右边的序列“5 4”。对序列“2 1”以2为基准数进行调整,处理完毕之后的序列为“1 2”,到此2已经归位。序列“1”只有一个数,也不需要进行任何处理。至此我们对序列“2 1”已全部处理完毕,得到序列是“1 2”。序列“5 4”的处理也仿照此方法,最后得到的序列如下:

1 2 3 4 5 6 9 7 10 8

对于序列“9 7 10 8”也模拟刚才的过程,直到不可拆分出新的子序列为止。最终将会得到这样的序列,如下

1 2 3 4 5 6 7 8 9 10

到此,排序完全结束。细心的同学可能已经发现,快速排序的每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止,排序就结束了。下面上个霸气的图来描述下整个算法的处理过程。
在这里插入图片描述
这是为什么呢?

快速排序之所比较快,因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的都是O(N2),它的平均时间复杂度为O(NlogN)。其实快速排序是基于一种叫做“二分”的思想。我们后面还会遇到“二分”思想,到时候再聊。先上代码,如下

快速排序代码实现

其实快速排序有一个比较简单的思想,就是递归。对于每一趟排序都是一样的思想,只不过需要进行排序的数组的范围越来越小了,使用递归实现这种排序最适合不过了。

import com.sun.deploy.util.StringUtils;

public class tets {
    
    public static void main(String[] args) {

        int[] data = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};

        System.out.println("排序之前:\n" + java.util.Arrays.toString(data));

        quickSort(data, 0, data.length - 1);

        System.out.println("排序之后:\n" + java.util.Arrays.toString(data));
    }


    public static void quickSort(int[] data, int low, int high) {
        int i, j, temp, t;
        if (low > high) {
            return;
        }
        i = low;
        j = high;
        //temp就是基准位
        temp = data[low];
        System.out.println("基准位:" + temp);

        while (i < j) {
            //因为所排列顺序是递增,而且每次哨兵排查结束在于左右哨兵是否相遇,所以右边哨兵负责寻找大于基准的数,左边哨兵寻找小于基准的数
            // ,如若需要改变快排后的结果顺序只需要改变左右哨兵对于基准数的大小判断
            //先看右边,依次往左递减 在未和左边哨兵相遇前寻找大于或者等于基准的数
            while (temp <= data[j] && i < j) {
                j--;
            }
            //再看左边,依次往右递增 在未和右边哨兵相遇前寻找小于或者等于基准的数
            while (temp >= data[i] && i < j) {
                i++;
            }
            //如果满足条件则交换  左右哨兵未相遇的情况下寻找到满足条件的数
            if (i < j) {
                System.out.println("交换:" + data[i] + "和" + data[j]);
                t = data[j];
                data[j] = data[i];
                data[i] = t;
                System.out.println(java.util.Arrays.toString(data));
            }
        }
        //最后将基准位与i和j相等位置的数字交换 左右哨兵相遇
        System.out.println("基准位" + temp + "和i、j相遇的位置" + data[i] + "交换");
        data[low] = data[i];
        data[i] = temp;
        System.out.println(java.util.Arrays.toString(data));

        //此时基准数左边都比基准数小,右边都比基准数大,故而基准数位置不变,基准数左右两边重新进行快排
        //递归调用左半数组
        quickSort(data, low, j - 1);
        //递归调用右半数组
        quickSort(data, j + 1, high);
    }
}

快速排序的特点及性能
快速排序是在冒泡排序的基础上改进而来的,冒泡排序每次只能交换相邻的两个元素,而快速排序是跳跃式的交换,交换的距离很大,因此总的比较和交换次数少了很多,速度也快了不少。

但是快速排序在最坏情况下的时间复杂度和冒泡排序一样,是 O(n2),实际上每次比较都需要交换,但是这种情况并不常见。我们可以思考一下如果每次比较都需要交换,那么数列的平均时间复杂度是 O(nlogn),事实上在大多数时候,排序的速度要快于这个平均时间复杂度。这种算法实际上是一种分治法思想,也就是分而治之,把问题分为一个个的小部分来分别解决,再把结果组合起来。

快速排序只是使用数组原本的空间进行排序,所以所占用的空间应该是常量级的,但是由于每次划分之后是递归调用,所以递归调用在运行的过程中会消耗一定的空间,在一般情况下的空间复杂度为 O(logn),在最差的情况下,若每次只完成了一个元素,那么空间复杂度为 O(n)。所以我们一般认为快速排序的空间复杂度为 O(logn)

快速排序是一个不稳定的算法,在经过排序之后,可能会对相同值的元素的相对位置造成改变。

快速排序基本上被认为是相同数量级的所有排序算法中,平均性能最好的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

快速排序算法详解(原理,时间复杂度,实现代码) 的相关文章

随机推荐

  • 链语BTChat力推“加密+社交” 引领区块链新社交时代

    近些年来互联网的发展日新月异 大数据化 人工智能 物联网这些都在成为人们生活中触手可及的东西 而区块链技能则被认为是继互联网之后最具颠覆性的创新技术 此前区块链技术在金融服务业 游戏 供应链等不同的产业中都有着广泛应用 同样的对于社交平台而
  • wsfuzzer video

    http www neurofuzz com modules software vidz php
  • 14-4_Qt 5.9 C++开发指南_QUdpSocket实现 UDP 通信_UDP组播

    文章目录 1 UDP组播的特性 2 UDP 组播实例程序的功能 3 组播功能的程序实现 4 源码 4 1 可视化UI设计 4 2 mainwindow h 4 3 mainwindow cpp 1 UDP组播的特性 下图简单表示了组播的原理
  • Golang连接Jenkins获取Job Build状态及相关信息

    文章目录 1 连接Jenkins 2 controller 3 module 4 router 5 效果展示 第三方包 gojenkins 方法文档 gojenkins docs 实现起来很简单 利用第三方库 连接jenkins 调用相关方
  • flutter解决键盘顶起页面

    前言 flutter中解决键盘顶起页面的问题 flutter 1 Scaffold resizeToAvoidBottomPadding return Scaffold resizeToAvoidBottomPadding false 解决
  • 使用OpenCV与深度学习从视频和图像中精准识别人脸: Python实践指南

    第一部分 引言与背景 人脸识别已经成为了当代技术领域中最热门和广泛应用的话题之一 从智能手机的解锁功能到机场的安全检查 人脸识别技术无处不在 在这篇文章中 我们将使用Python中的OpenCV库和深度学习模型 深入探讨如何从视频和图像中精
  • js 对数组对象进行排序

    let listData id 1 name 测试1 presenttime 1557883600000 id 2 name 测试2 presenttime 1580751813000 id 3 presenttime 1561448381
  • svn版本号,命令中-r 2和@2的区别

    问题 假设有一个svn repository是 svn 192 168 2 6 project 在版本1 20的svn里 存在 svn 192 168 2 6 project branches branch test 在版本21时 由于br
  • BUUCTF-Web-命令执行-[ACTF2020 新生赛]Exec

    BUUCTF Web 命令执行 ACTF2020 新生赛 Exec 题目链接 BUUCTF 类型 命令注入 知识点 命令拼接符 解题过程 这道题目比较简单 打开发现是一个ping命令执行页面 使用post接受参数 测试命令拼接符 发现未进行
  • CMW500测试设置及问题处理

    测试CATM1需要打开eMTC Auto Mode 最新的U BLOX R510S模块 这里需要设置为RMC模式 设置为eMTC Auto Mode会出现连接后就断开的情况 没法测试 Measure subframe设置为5 不同的band
  • Kubernetes生产实践系列之三十一:Kubernetes基础技术之CPU资源的调度和管理(CFS)

    一 前言 在使用Kubernetes的过程中 我们看到过这样一个告警信息 K8S 告警主题 CPUThrottlingHigh 告警级别 warning 告警类型 CPUThrottlingHigh 故障实例 告警详情 27 throttl
  • android bluetooth UUID蓝牙查询表

    UUID是 Universally Unique Identifier 的简称 通用唯一识别码的意思 对于蓝牙设备 每个服务都有通用 独立 唯一的UUID与之对应 也就是说 在同一时间 同一地点 不可能有两个相同的UUID标识的不同服务 以
  • .Net C# 使用 IKVM 调用 Java 代码

    相关开源库 https github com ikvm revived 版本号 Net 6 JDK 8 IKVM 8 2 1 IKVM 在 8 2 0 版本中新增加 IkvmReference 在 MSBuild 中配置 自动帮你编译jar
  • 虚拟机打开vim文件以后退出方式

    如果是vi 则 Esc 退出编辑模式 输入以下命令 wq 保存后退出vi 若为 wq 则为强制储存后退出 常用 w 保存但不退出 常用 w 若文件属性为 只读 时 强制写入该档案 q 离开 vi 常用 q 若曾修改过档案 又不想储存 使用
  • python制作查询工具发给别人使用_Python 制作查询商品历史价格的小工具

    一年一度的双十一就快到了 各种砍价 盖楼 挖现金的口令将在未来一个月内充斥朋友圈 微信群中 玩过多次双十一活动的小编表示一顿操作猛如虎 一看结果2毛5 浪费时间不说而且未必得到真正的优惠 双十一电商的 明降暗升 已经是默认的潜规则了 打破这
  • 为何在新建STM工程中全局声明两个宏

    在uVision中新建STM32工程后 需要从STM32标准库中拷贝标准外设驱动到自己的工程目录中 此时需要在工程设置 gt C C 选项卡下的Define文本框中键入这两个全局宏定义 STM32F40 41xxx USE STDPERIP
  • 二叉树的一些练习题

    前言 二叉树的简单题目 通过画栈帧图去理解过程 画一画 走一走递归过程 理解会更加深刻 二叉树练习题 前言 二叉树的创建 二叉树先序遍历创建 PreCreat 二叉树层次创建 LevelCreat 二叉树的销毁 BinaryTreeDest
  • 二分法查找数组元素

    二分法查找元素时可以节省下极高的效率 如果有2的32次方个元素 依次查找需要查找2的32次方次 然而二分查找最多只用查找32次 程序执行的时间极大的缩短 二分法查找元素 include
  • nginx中location里面的try_files配置导致Vue设置history模式下的请求丢失参数

    nginx中location里面的try files配置导致vue设置history模式下的请求丢失参数 背景描述 在一次生产环境中 vue使用history模式在访问地址的参数会丢失 地址栏也会变成没有参数的地址 并且请求会发生301重定
  • 快速排序算法详解(原理,时间复杂度,实现代码)

    快速排序算法详解 原理 实现和时间复杂度 快速排序是对冒泡排序的一种改进 由 C A R Hoare Charles Antony Richard Hoare 东尼 霍尔 在 1962 年提出 快速排序的基本思想是 通过一趟排序将要排序的数