要学习快速排序,首先要理解的一个重要操作是 Partition。
理解 Partition
Partition 其实并不难理解,它的基本思想如下所述。
Partition 每一次排定一个元素,这个元素不像选择排序或者冒泡排序那样排在数组的开头或者末尾,可以在数组的任何位置,既然是排定它,那么它就要在数组最终确定的位置上,所以它具有这样的性质:位于它前面的元素都小于它,位于它后面的元素都大于等于它。
例如数组是 [4,6,2,3,1,5,7,8]。我们首先选择数组的第 个元素
。经过 Partition 以后,我们要达到的是这样一个效果:
[2,3,1,4,6,5,7,8],这个数组有什么特点呢?

1、我们刚开始选定的元素 就放在了最终它应该放在的位置上;
2、元素 之前的所有元素都比它小,严格说应该是“不大于”
,并且它们的相对顺序和 Partition 以前一致;
3、对称地,元素 之后的所有元素都比它大,严格说应该是“不小于”
,并且它们的相对顺序和 Partition 以前一致。
这是怎么做到的呢?其实很简单,我们就拿 作为“基准元素”,在剩下的数组元素中扫一遍,比
的“依次”拿出来形成“列表1”,比
大的“依次”拿出来形成“列表2”,那么
[列表1, 元素 4, 列表2 ] 就是上面的结果。在 Python 中,你可以很轻松地实现这个操作。
Python 代码:
def partition(nums):
pivot = nums[0]
return [i for i in nums[1:] if i < pivot] + [pivot] + [i for i in nums[1:] if i >= pivot]
if __name__ == '__main__':
nums = [4, 3, 1, 2, 7, 8, 5]
result = partition(nums)
print(result)
而快速排序是这样的:
Python 代码:
def quick_sort(nums):
if len(nums) <= 1:
return nums
pivot = nums[0]
left = [num for num in nums[1:] if num <= pivot]
right = [num for num in nums[1:] if num > pivot]
return quick_sort(left) + [p] + quick_sort(right)
如果你没有看懂这里 Python 的语法,也没有关系。Python 就是这么神奇,看起来像伪代码一样,但它真的可以运行。快速排序的逻辑在上面这段代码中也很清晰了:每次排定一个元素,因为排定以后满足上面提到的性质2和性质3,然后对这个元素的左边和右边再递归进行下去。是不是看到了“分治”的影子?只不过他不像归并排序那样,最后要“合”起来。因为一开始,我们不是“无脑地”一分为二,我们做了上面的性质2和性质3,这一点要仔细体会。
当然,我们不会这么做,因为这样做其实是使用了辅助的空间。实际上,我们可以通过交换数组的元素达到同样的效果。这个操作是非常经典的,是需要在理解的基础上记忆的方法。

Python 代码:
def partition(nums, left, right):
pivot = nums[left]
j = left
for i in range(left + 1, right + 1):
if nums[i] < pivot:
j += 1
nums[j], nums[i] = nums[i], nums[j]
nums[left], nums[j] = nums[j], nums[left]
return j
在编写这个方法的过程中,我们使用了两个“指针”变量:i 和 j。i 用于遍历,表示我要把除了 pivot 的剩下所有数组元素全部看完。而 j 维护了一个循环不变的性质,就是区间 [left,j) 内的元素都严格小于 pivot,这是代码编写的关键之处,注意这里 j 并不包含,所以初始值设置为 left,区间 [left,left) 内的元素为空,这也是合理的。接下来,只要遇到比 pivot 小的元素,就交换到 j 这个位置,因此 j + 1 就表示下一个要交换到这里的元素的位置。把数组中剩下的元素全部看过一遍以后,再把 j 和 left 交换就可以了。
最后,我们要把 j 返回回去,供快速排序后序调用。
刚开始接触的时候,可能会比较 (๑•ᴗ•๑) ,要反复看,用几个小数组手写一遍程序运行的过程。并且在自己编写的代码中添加一些打印输出,观察自己写的代码是不是按照自己认为的那个逻辑在运行。
例如,我在编写代码的时候,就会以注释的形式写上我理解的 partition 过程。

我们再画一个简单的图来加深对“大放过,小操作”这个过程的理解。

下面是我以前用 Java 写的时候的笔记:

在理解了 Partition 的基础,我们不难写出第 1 版快速排序的代码。
编写第 1 版快速排序
Python 代码:
def partition(nums, left, right):
pivot = nums[left]
j = left
for i in range(left + 1, right + 1):
if nums[i] < pivot:
j += 1
nums[j], nums[i] = nums[i], nums[j]
nums[left], nums[j] = nums[j], nums[left]
return j
def __quick_sort(nums, left, right):
if left >= right:
return
p_idx = partition(nums, left, right)
__quick_sort(nums, left, p_idx - 1)
__quick_sort(nums, p_idx + 1, right)
def quick_sort(nums):
__quick_sort(nums, 0, len(nums) - 1)
写好以后,不妨用前面我们介绍的方法,自己写一个生成随机数组的方法,再写一个判断数组是否有序的方法,来检验我们自己编写的排序算法是否正确。
我可以,你也一定可以!

看到这样的输出,心里很欣慰,居然写对了!
下面是我自己编写的测试代码,供参考,你们完全可以写出比我写的更好的代码。



下面这个方法看起来有点“傻”。

本文源代码
(本节完)