这是我参与2022首次更文挑战的第40天,活动详情查看:2022首次更文挑战。
飞快的递归算法
递归给我们带来了新的算法实现方式,例如上一章的文件系统遍历。
前几章我们学会了一些排序算法,包括冒泡排序、选择排序和插入排序。但在现实中,数组排序不是通过它们来做的。为了免去大家重复编写排序算法的烦恼,大多数编程语言都自带用于数组排序的函数,其中很多采用的都是快速排序。
虽然它已经实现好了,但我们还是想研究一下它的原理,因为其运用递归来给算法提速的做法极具推广意义。
快速排序真的很快。尽管在最坏情况(数组逆序)下它跟插入排序、选择排序的效率差不多,但在日常多见的平均情况中,它的确表现优异。
快速排序依赖于一个名为分区的概念,所以我们先从它开始了解。
分区
此处的分区指的是从数组随机选取一个值,以其为轴,将比它小的值放到它左边,比它大的值放到它右边。分区的算法实现起来很简单,例子如下所示。
假设有一个下面这样的数组。
从技术上来说,选任意值为轴都可以,我们就以数组最右的值为轴吧。现在轴就是3了,我们把它圈起来。
然后放置指针,它们应该分别指向排除轴元素的数组最左和最右的元素。
接着就可以分区了,步骤如下。
(1)左指针逐个格子向右移动,当遇到大于或等于轴的值时,就停下来。
(2)右指针逐个格子向左移动,当遇到小于或等于轴的值时,就停下来。
(3)将两指针所指的值交换位置。
(4)重复上述步骤,直至两指针重合,或左指针移到右指针的右边。
(5)将轴与左指针所指的值交换位置。
当分区完成时,在轴左侧的那些值肯定比轴要小,在轴右侧的那些值肯定比轴要大。因此,轴的位置也就确定了,虽然其他值的位置还没有完全确定。
让我们来把此流程套到示例数组上。
第1步:拿左指针(正指向0)与轴(值为3)比较。
由于0比轴小,左指针可以右移。
第2步:右移左指针。
将左指针(值为5)与轴比较。它比轴小吗?不。于是左指针停在这里,下一步我们启动右指针。
第3步:比较右指针(值为6)和轴。它比轴大吗?对。于是右指针左移。
第4步:左移右指针。
比较右指针(值为1)和轴。它比轴大吗?不。于是右指针停下。
第5步:因为两个指针都停住了,所以交换它们的值。
随后,再次启动左指针。
第6步:右移左指针。
比较左指针(值为2)和轴。它比轴小吗?对。于是继续右移。
第7步:左指针移到下一格子。注意,这时两个指针都指向同一个值了。
比较左指针和轴。由于左指针的值比轴要大,我们将其停在那里。而且现在左指针与右指针重合,无须再移动指针了。
第8步:到了分区的最后一步,将左指针的值与轴交换位置。
虽然数组还没完全排好序,但我们已完成了一次分区。即比轴(值为3)小的值都聚在了它的左侧,比轴大的值都聚在了它的右侧,这就意味着3已经被放置到正确的位置上了。