基本排序算法之快速排序的Python实现
上一篇中简单实现了冒泡排序,但是这种基于反复交换的算法,每次只比较相邻的两个数,处理大规模数据时效率相当低下,这时候另一种交换算法 - 快速排序就有用武之地了。
顾名思义,快速排序的效率会高一些,其原理也很简单,是在交换算法中使用“分治法”的思想,大致如下:
- 如果待排序list中元素数目为0或1这返回;
- 在list中任取一个元素作为枢纽(pivot);
- 改pivot将list分割成两部分,小于等于pivot的元素列表作为small_list,大于pivot的作为greater_list;
- 对small_list与greater_list重复上述步骤,递归调用直至无法分割。此时得到最终排序后的order_list。
基于这种原理,很容易用Python3的方法实现如下:
1 | def quick_sort1(lists): |
随机生成5个数测试一下,结果如下图所示:
可以看出虽然排序结果没有问题,但是递归的过程导致装饰器反复计时,以及先为空的列表也被反复打印提示。
首先解决计时的问题,这是python语法糖引起的,用@time_usage
修饰的quick_sort1函数,其实相当于quick_sort1 = time_usage(quick_sorted1)
,于是递归的时候每次都要在它的作用域内执行时间函数,所以这里就不用语法糖实现它,而改成time_usage(quick_sort1)(RAND_LIST)
直接调用就可以了。
另一方面,由于这个实现开辟了新的空间,导致递归中不停的出现空列表,所以会打印出一堆提示来,所以将它改成“就地”排序的操作,即在被分割的列表里直接交换元素,而不把它们存在新的列表里,改进后代码如下:
1 | def quick_sort2(lists): |
这样直接在原列表里操作,当然也直接改变了原本的列表元素顺序,下面用它与第二种冒泡排序以及内置sorted函数对比一下效率,使用990个随机数得出的结果如下:
相比第二种冒泡排序,确实快了一些,但是如果我们使用大的数据量,例如当排序10000个数时,发现其会报错,内容为RecursionError: maximum recursion depth exceeded in comparison
,因为已经超出了python的默认的递归深度,虽然我们可以用import sys; sys.setrecursionlimit(20000)
这种方法将递归深度加大,但是这并不是一种优雅的做法。
在别的一些编程语言里,我们可以使用尾递归来实现快速排序,但是python并没有尾递归优化,所以这里并不是明智的选择;递归中的这种问题大多可以转化成迭代去解决,但是由于需要利用栈手动保存中间变量,实际效率会降低一些,这里就不去实现了。
快速排序中,选取枢纽元其实也会影响算法的执行效率,目前对此算法常用的选取枢纽元的方法为“三数中值分割法”,即选取左端、右端以及中心位置上的三个元素的中值作为枢纽元。
另一方面,相比冒泡排序,快速排序属于不稳定排序,这里引用维基百科对于稳定性的解释:
稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
而快速排序可能会改变原有稳定的键值对顺序,因此是不稳定的。