使用Python进行快速排序
在现实生活中,我们应该始终使用Python提供的内置排序。然而,理解快速排序算法是有益的。
我的目标是将这个主题分解,使读者能够轻松理解并复制它,而不必返回参考材料。
快速排序算法基本上是这样的:
- 选择一个基准数据点。
- 将所有小于(下面)基准的数据点移动到基准下方的位置 - 将大于或等于(上面)基准的数据点移动到基准上方的位置。
- 将算法应用于基准上方和下方的区域
如果数据是随机分布的,则选择第一个数据点作为基准相当于随机选择。
易读的示例:
首先,让我们看一个易读的示例,它使用注释和变量名指向中间值:
def quicksort(xs):
"""Given indexable and slicable iterable, return a sorted list"""
if xs:
pivot = xs[0]
below = [i for i in xs[1:] if i < pivot]
above = [i for i in xs[1:] if i >= pivot]
return quicksort(below) + [pivot] + quicksort(above)
else:
return xs
重新陈述这里展示的算法和代码 - 我们将大于基准点的值移到右边,将小于基准点的值移到左边,然后将这些分区传递给同一函数以进一步排序。
精简版:
此可压缩为88字符:
q=lambda x:x and q([i for i in x[1:]if i<=x[0]])+[x[0]]+q([i for i in x[1:]if i>x[0]])
为了看到我们是如何达成这一点的,首先需要拿出我们易读的示例,删除注释和文档字符串,并在原地找到枢轴点:
def quicksort(xs):
if xs:
below = [i for i in xs[1:] if i < xs[0]]
above = [i for i in xs[1:] if i >= xs[0]]
return quicksort(below) + [xs[0]] + quicksort(above)
else:
return xs
现在在原地找到下面和上面的内容:
def quicksort(xs):
if xs:
return (quicksort([i for i in xs[1:] if i < xs[0]] )
+ [xs[0]]
+ quicksort([i for i in xs[1:] if i >= xs[0]]))
else:
return xs
现在,我们知道如果
and
返回false,则返回前一个元素,否则如果它为true,则评估并返回下一个元素,因此我们有:
def quicksort(xs):
return xs and (quicksort([i for i in xs[1:] if i < xs[0]] )
+ [xs[0]]
+ quicksort([i for i in xs[1:] if i >= xs[0]]))
由于Lambda表达式返回单个表达式,而我们已经简化为单个表达式(尽管它变得更加难以阅读),因此现在我们可以使用Lambda:
quicksort = lambda xs: (quicksort([i for i in xs[1:] if i < xs[0]] )
+ [xs[0]]
+ quicksort([i for i in xs[1:] if i >= xs[0]]))
为了简化我们的示例,将函数和变量名称缩短为一个字母,并消除不必要的空格。
q=lambda x:x and q([i for i in x[1:]if i<=x[0]])+[x[0]]+q([i for i in x[1:]if i>x[0]])
请注意,这个lambda(像大多数代码高尔夫一样)风格相当糟糕。
使用Hoare分区方案的原地快速排序
先前的实现创建了许多不必要的额外列表。如果我们可以原地完成,就会避免浪费空间。
以下实现使用Hoare分区方案,您可以在维基百科上
阅读更多(但我们显然通过使用while循环语义而非do-while以及将缩小步骤移到外部while循环的末尾,每个
partition()
调用已删除了最多4个冗余计算)。
def quicksort(a_list):
"""Hoare partition scheme, see https://en.wikipedia.org/wiki/Quicksort"""
def _quicksort(a_list, low, high):
if low < high:
p = partition(a_list, low, high)
_quicksort(a_list, low, p)
_quicksort(a_list, p+1, high)
def partition(a_list, low, high):
pivot = a_list[low]
while True:
while a_list[low] < pivot:
low += 1
while a_list[high] > pivot:
high -= 1
if low >= high:
return high
a_list[low], a_list[high] = a_list[high], a_list[low]
low += 1
high -= 1
_quicksort(a_list, 0, len(a_list)-1)
return a_list
不确定我是否彻底测试了它:
def main():
assert quicksort([1]) == [1]
assert quicksort([1,2]) == [1,2]
assert quicksort([1,2,3]) == [1,2,3]
assert quicksort([1,2,3,4]) == [1,2,3,4]
assert quicksort([2,1,3,4]) == [1,2,3,4]
assert quicksort([1,3,2,4]) == [1,2,3,4]
assert quicksort([1,2,4,3]) == [1,2,3,4]
assert quicksort([2,1,1,1]) == [1,1,1,2]
assert quicksort([1,2,1,1]) == [1,1,1,2]
assert quicksort([1,1,2,1]) == [1,1,1,2]
assert quicksort([1,1,1,2]) == [1,1,1,2]
结论
这个算法经常在计算机科学课程中教授,并在工作面试中被要求。它帮助我们思考递归和分治。
由于Python内置的timsort算法非常高效,并且我们有递归限制,因此快速排序在Python中并不是很实用。我们期望使用list.sort
原地对列表进行排序,或使用sorted
创建新的排序列表 - 两者都需要key
和reverse
参数。
my_list = list1 + list2 + ...
。或者将列表解包到新列表中my_list = [*list1, *list2]
。 - Mark Mishyn