快速排序(Quicksort)详解

article/2025/9/22 7:19:35

引言

这篇文章是我在2015年写的,当时正在看算法导论中关于快排的部分,因此写下文章总结一下当时对快排的理解。这几天我一直在review一下我先前写的blog,发现有些地方写的不算太好,还有一些错误的地方。今天我重新修改一下这篇文章,把错误的地方修正过来,并补充一些新的内容。

初识Quicksort

Quicksort是一个分而治之的算法,它根据主元把一个大数组分成2个小数组:其中1个数组的元素要比主元小,另一个要比主元大。Quicksort至今依然是一个常用的排序算法,如果算法实现好的情况下,它的速度要比merge sort 和 heapsort快2到3倍。一个有效实现地Quicksort 并不是一个stable sort,即相等元素的相对顺序不能被保证。Quicksort 也可以在一个数组上进行原址排序。数学分析表明,quicksort排序n个元素平均需要 O(nlogn) O ( n l o g n ) 比较,在最坏的情况下,它需要 O(n2) O ( n 2 ) 次比较,虽然这样的行为不常见。

Quicksort的步骤

Quicksort主要包含以下3步:

  1. 从数组中取出一个元素,叫做主元(pivot)
  2. 重排序数组,使得所有小于pivot的元素在它前面,所有大于pivot的元素在它后面,等于pivot的元素放在哪面都行。这样的划分以后,pivot的位置已经排好了。这个过程叫做partition操作
  3. 递归地应用步骤2到小于pivot的子数组和大于pivot的子数组

在上面的步骤中,递归的base case是数组的大小为0或1,因为这样的数组已经有序,不需要再排序。我们有很多方式来选择pivot,不同地方式会对算法的性能有很大地影响。下图是我从普林斯顿算法书的官方网站上找到的,它演示了 Quicksort 的排序过程,大家参考一下。

Quicksort

Lomuto partition scheme

由于编程珠玑和算法导论这2本很有名的书中介绍的都是这种 partition 模式,因此它被大家所熟知。这个 partition 模式选择数组中的最后一个元素作为 pivot. As this scheme is more compact and easy to understand, it is frequently used in introductory material, although it is less efficient than Hoare’s original scheme. This scheme degrades to O(n2) O ( n 2 ) when the array is already in order. 关于这种模式的 pseudocode 如下:

algorithm quicksort(A, lo, hi) isif lo < hi thenp := partition(A, lo, hi)quicksort(A, lo, p - 1 )quicksort(A, p + 1, hi)algorithm partition(A, lo, hi) ispivot := A[hi]i := lo - 1    for j := lo to hi - 1 doif A[j] < pivot theni := i + 1swap A[i] with A[j]if A[hi] < A[i + 1] thenswap A[i + 1] with A[hi]return i + 1

Sorting the entire array is accomplished by quicksort(A, 0, length(A) - 1).

Hoare partition scheme

Hoare’s scheme is more efficient than Lomuto’s partition scheme because it does three times fewer swaps on average, and it creates efficient partitions even when all values are equal. Like Lomuto’s partition scheme, Hoare partitioning also causes Quicksort to degrades to O(n2) O ( n 2 ) when the input array is already sorted; it also doesn’t produce a stable sort. 下面是我从算法导论中习题7-1中截取下来的 pseudocode.

Hoare partition scheme

下面我来回答一下这个习题!

关于这个 partition 的循环不变式为:

  • 对于任意下标a, pa<i p ≤ a < i , 有 A[a]x A [ a ] ≤ x
  • 对于任意下标b, j<br j < b ≤ r , 有 A[b]x A [ b ] ≥ x

1、When Hoare-Partition terminates, it returns a value j such that pj<r p ≤ j < r

从程序可以看出,当 while 循环开始时,j = j - 1,此时 j = r 了; 紧接着程序执行,i=i+1=p,循环终止,由于A[p] 是 pivot,所以此时A[i]=x,这就又给了j循环一次的机会,因此 j<r j < r . 根据循环不变式, pa<i p ≤ a < i 的元素都是小于或等于x的,因此j最多到p一定会终止。

2、The indices i and j are such that we never access an element of A outside the subarray A[p … r]

在问题1中,已经证明了 pj<r p ≤ j < r . 类似的逻辑,我们可以证明 pir p ≤ i ≤ r

原始版本Java实现及其性能分析

代码如下:

public static void quickSort(int[] a, int lo, int hi) {if (lo < hi) {int pivot = partition(a, lo, hi);quickSort(a, lo, pivot - 1);quickSort(a, pivot + 1, hi);}}public static int partition(int[] a, int lo, int hi) {int x = a[lo];int j = hi + 1;for (int i = hi; i > lo; i--) {if (a[i] >= x) {j--;swap(a, i, j);}}swap(a, lo, j - 1);return j - 1;}

算法时间复杂度:上面算法如果在最坏情况(数组中的元素已经有序)下,时间复杂度为Θ(n^2);期望运行时间为Θ(nlogn)。

算法空间复杂度:由于快速排序为原址排序,所以其主要的空间复杂度来自于递归调用,每一次递归调用将在调用栈上创建一个栈帧。假设每一次传递数组的信息是采用指针的方式,那么每一次递归调用可以认为其空间复杂度为O(1)。那么算法在最坏情况下,有Θ(n)次递归调用,所以此时其空间复杂度为Θ(n);如果算法在平均情况下,其空间复杂度为O(logn)。

随机化版本Java实现及其性能分析

代码如下:

public static int randomized_partition(int[] a, int lo, int hi) {int i = new Random().nextInt(hi - lo + 1) + lo;swap(a, lo, i);return partition(a, lo, hi);}

这个版本的实现只是对原始版本的代码稍作改动,只不过将随机在数组中选择一个主元,并与数组中的第一个元素进行交换。上述代码中的partition方法与原始版本相同。

算法时间复杂度:虽然我们对程序在最坏情况下的运行时间感兴趣,但是在随机化版本中,它并没有改变最坏情况下的运行时间,它只是减少了出现最坏情况的可能性。因此,我们只分析算法的期望运行时间,而不是其最坏运行时间。它的期望运行时间是Θ(nlogn)。

算法空间复杂度:由于这个版本的算法只是减少最坏情况出现的可能性,所以其空间复杂度与原始版本的分析一致。

Hoare版本Java实现及其性能分析

代码如下:

public static void quickSort(int[] a, int lo, int hi) {if (lo < hi) {int pivot = hoare_partition(a, lo, hi);quickSort(a, lo, pivot);quickSort(a, pivot + 1, hi);}
}public static int hoare_partition(int[] a, int lo, int hi) {int x = a[lo];int i = lo - 1;int j = hi + 1;while (true) {doj--;while (a[j] > x);doi++;while (a[i] < x);if (i < j)swap(a, i, j);elsereturn j;}
}

这个版本中的quickSort方法,第一行递归不是pivot - 1而是pivot。这是因为当hoare_partition结束时,只能保证a[p…j]中的每一个元素都小于或等于a[j+1…r]中的元素,其所选取的主元并没有就位。

这个版本的算法在含有许多重复元素的情况下,可以避免其出现最坏情况的划分。

算法时间复杂度:由于这个版本的算法并没有杜绝最坏情况的出现,所以分析同上面两个版本。

算法空间复杂度:由于这个版本的算法并没有杜绝最坏情况的出现,所以分析同上面两个版本。

通过尾递归改变最坏情况下的空间复杂度

我们知道,对于任何一种算法改进的版本来说,都不可能完全避免最坏情况的出现,它们只是减小其出现的机率。但是改变最坏情况下的空间复杂度是可能做到的。我们通过递归调用元素少的那部分,对于元素多的那部分,我们改写尾递归。只要元素少的那部分总是小于或等于输入规模的一半,那么递归调用至多为O(logn)。

代码如下:

public static void tailRecursiveQuickSort(int[] a, int lo, int hi) {while (lo < hi) {int pivot = partition(a, lo, hi);//partition方法与上述版本相同if ( pivot - lo < hi - pivot ) {quickSort(a, lo, pivot - 1);lo = pivot + 1;} else {quickSort(a, pivot + 1, hi);hi = pivot - 1;}}
}

算法时间复杂度:由于这个版本的算法并没有杜绝最坏情况的出现,所以分析同上面两个版本。

算法空间复杂度:这个版本的算法其递归深度至多为logn,所以其空间复杂度为O(logn)


http://chatgpt.dhexx.cn/article/M3Wjfou9.shtml

相关文章

排序——快速排序(Quick sort)

概况 快速排序&#xff08;Quick sort&#xff09;是对冒泡排序的一种改进。快速排序由C. A. R. Hoare在1960年提出。 算法思路 通过一趟排序将要排序的数据分割成独立的两部分&#xff0c;其中一部分的所有数据都比另外一部分的所有数据都要小&#xff0c;然后再按此方法对…

Visual Studio 2013 + Intel(R) Visual Fortran 安装教程

安装了好几遍&#xff0c;ivf都不能加载到vs中&#xff0c;中途放弃了vs&#xff0c;又改用codeblocks编译器&#xff0c;依然在写fortran的时候出现了各种问题&#xff0c;于是又放弃了codeblocks改回vs&#xff0c;总之是折腾了好久&#xff0c;终于装明白了ivf并且将fortran…

fortan dll在本地可以运行成功,移植到其他电脑上报错Exception in thread main java.lang.UnsatisfiedLinkError: 找不到指定的模块。

坑&#xff01;&#xff01;&#xff01;大大的坑&#xff01;&#xff01;&#xff01; 本项目需要实现java调用fortran的dll&#xff0c;我在本地编译好apae.dll&#xff0c;然后java调用dll成功&#xff01; 将apae.dll发送给对方&#xff0c;同样的java代码和fortran的dl…

Matlab2017b配置C++/C/Fortan编译器的问题(已解决)

今天在配置Matlab2017编译C代码的时候&#xff0c;一贯的调用mex -setup命令&#xff0c;结果显示没有找到任何支持的编译器或SDK。您可以安装免费的mingw-w64 c/c编译器; 崩溃&#xff01;&#xff01; 最后根据错误所给的链接在matlab2017的获取附加功能安装mingw-w64 、结…

Fortan写出数据到CSV文件中

1.输入代码 program HelloWorldopen(unit60, fileF:\Lambda3.csv)write(60,*) 4.2,5.2/sqrt(2*12550/4.1595e-11)end program HelloWorld 2.结果如下

ABAQUS6.10 VS2008 Intel fortan11.1

1.先参考微博 http://blog.sina.com.cn/s/blog_56d5b3b00100wg2y.html 2. 其它一些资料以及注意事项 IVF 11.1 可用 http://pan.baidu.com/s/1pKWPCq3 用DAEMON tools 安装IVF11.1 DAEMON是个虚拟光驱 VS2008 用英文版 ABAQUS用6.10 6.11 6.12 都安装完之后 找到Commands文件夹…

fluent加载第三方(C++,Fortan等)动态链接库

这里我介绍一种比较简单的方法&#xff0c;首先我们从ANSYS Fluent UDF Manual上随便找一段正确的UDF&#xff0c;下面这段UDF取自ANSYS 18的ANSYS Fluent UDF Manual&#xff0c;位于2.3.23.3. Example 1 - Pressure Profile / *********************************************…

Abaqus运行fortan报错:“Error in job Job-1: Problem during compilation - D:\test.for”

一、简介 最近在学习abaqus的子程序&#xff1b;学习视频为简单实例_哔哩哔哩_bilibili 模型建立后&#xff0c;所写的子程序代码如下&#xff1a; SUBROUTINE DLOAD(F,KSTEP,KINC,TIME,NOEL,NPT,LAYER,KSPT,1 COORDS,JLTYP,SNAME) CINCLUDE ABA_PARAM.INC CDIMENSION TIME(2),…

linux安装go语言环境

linux安装go语言环境 前言环境版本前期知识准备部署步骤安装wget下载压缩包&#xff0c;解压到指定路径/usr/local配置环境变量创建GOPATH文件夹添加PATH环境变量and设置GOPATH环境变量1.打开配置文件2.更改配置信息3.然后执行下面命令使上述环境变量的设置立即生效&#xff1a…

如何修改linux的 系统语言

首先查看当前系统的语言 1、echo $LANG 查看当前操作系统的语言 中文&#xff1a;zh_CN.UTF-8 英文:&#xff1a;en_US.UTF-8 2、临时更改默认语言&#xff0c;当前立即生效 重启失效 export LANGen_US.UTF-8 3、永久生效,修改配置文件 centos7/rhel7之前版本&#x…

如何查看、修改linux系统语言

如何查看、修改linux系统语言 本机环境&#xff1a;CentOS 7.6本文方法适用于&#xff1a;centos7/rhel7之前版本和centos7/rhel7版本 [rootlocalhost ~]# cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) 1、查看当前操作系统的语言 echo $LANG 中文&#…

Linux安装日文语言包,以及,TeraTerm显示乱码问题 的 解决

■效果 ■语言包安装 rootsxzap01:~# apt -y install language-pack-ja ■修改配置文件&#xff08;修改之后&#xff0c;需要重新连接&#xff09; 改前&#xff1a;LANGen_US.UTF-8 vi /etc/default/locale 改后&#xff1a;LANGja_JP.UTF-8 ■乱码问题解决 ・修改这里…

Go语言 linux安装

​​​​​1、下载go安装包 载go的linux安装包&#xff0c;比如&#xff1a;go1.15.6.linux-amd64.tar.gz 2、解压 解压至/usr/local下&#xff0c;命令&#xff1a;tar -zxvf go1.15.6.linux-amd64.tar.gz 解压之后&#xff0c;查看版本&#xff1a;/usr/local/go/bin/go …

linux系统设置中文

怎么设置Linux系统中文语言&#xff0c;这是很多小伙伴在开始使用Linux的时候&#xff0c;都会遇到一个问题&#xff0c;就是终端输入命令回显的时候中文显示乱码。出现这个情况一般是由于没有安装中文语言包&#xff0c;或者设置的默认语言有问题导致的。以centos为例&#xf…

Linux 系统语言切换 ---- Linux Ubuntu 系统语言切换为英文

系统&#xff1a;Linux Ubuntu 18.04 中文版 目录 1. 在桌面右键打开命令框&#xff08;Open Terminal&#xff09;&#xff1a; 2. 进入系统默认语言设置文件目录&#xff1a; 3. 打开系统语言配置文件&#xff1a; 4. 打开的系统文件如下&#xff1a; 5. 修改系统文件内容…

设置linux-kali 2022语言为中文(保姆级图文)

目录 友情提示1. 打开终端2. 打开设置3. 修改设置4. 重启生效设置总结 欢迎关注 『网络工程专业』 系列&#xff0c;持续更新中 欢迎关注 『网络工程专业』 系列&#xff0c;持续更新中 在安装完 kali linux2022 时&#xff0c;操作系统默认语言为英文&#xff0c;初学者可以把…

Linux中如何切换中文英文

查看当前使用的是中文(zh_CN.UTF-8)还是英文(LANG"en_US.UTF-8): echo $LANG 进入etc文件&#xff1a; cd /etc 使用vim编辑器进入初始配置的locale.conf文件&#xff1a; vim locale.conf 可以看到我目前的locale.conf文件中只有中文&#xff0c;如图&#xff1a; 目…

嵌入式Linux开发的编程语言选择

欢迎大家关注我的公*号&#xff1a;embedded_bug 这里的嵌入式Linux环境是指非标准Linux发行版环境&#xff0c;比如通过buildroot创建的&#xff0c;相比于标准的Linux发行版比如ubuntu&#xff0c;debian&#xff0c;fedora&#xff0c;系统比较简陋&#xff0c;提供的库很有…

Linux如何修改系统语言

Linux如何修改系统语言 一、Linux如何修改系统语言 对于刚学Linux 的小伙伴&#xff0c;或者英语水平相对低一些的小伙伴&#xff0c;在自己的Linux系统里面如果能直接显示中文&#xff0c;中文提示就是比较爽的一件事了&#xff0c;接触起来也不会觉得头大&#xff0c;不过说…

将kali Linux系统的语言切换为中文

首先我们打开kali linux 虚拟机 。右键开启终端。.输入sudo apt install ttf-wqy- zenhei命令来下载中文语音包&#xff0c;这里我在第一次输入时没有加sudo命令&#xff0c;没有成功执行&#xff0c;原因是我的用户是普通用户&#xff0c;没有管理员权限&#xff0c;不能进行下…