快速排序(Quick Sort)同样是使用了分治法的思想,相比于其他的排序方法,它所用到的空间更少,因为其可以实现原地排序。同时如果随机选取中心枢(pivot),它也是一个随机算法。最重要的是,快速排序(Quick sort)的算法分析的过程非常给力。
本文首先描述问题,再说明快速排序(Quick Sort)的基本思路并给出伪代码,之后贴出自己的Python代码。在验证完算法的正确性之后,给出如何选择好的中心枢(pivot)的方法,即随机快速排序(Randomized Quick sort),并贴代码。最后进行算法复杂度分析。
问题描述
问题描述和其他排序算法一样,输入一组未排序的数组,如左边的数组,通过快速排序算法的计算,输出一组正确排序的数组,如右边的数组。
基本思路和伪代码
在给定的数组中选择一个元素作为中心枢(pivot),对数组重排列并分割(Partition),使得位于该中心枢(pivot)左边的元素都小于该元素,右边的元素都大于该元素,之后递归处理左右两组数。
这里值得注意的地方就是,每一次重排列之后,所选择的中心枢(pivot)元素所在的位置,就是最终排序结果中它应该在的位置。
1 QuickSort(array A, length n) 2 if n = 1 return 3 p = choosePivot(A, n) 4 Partition A around p 5 recursively sort 1st part 6 recursively sort 2st part
经过第4行的操作之后,位于左边的所有元素都小于p,而右边的数都大于p。下文中所有中心枢(pivot)元素都用p表示。
基于某一个p来对数组进行分块有两种实现的方法,第一种方法在内存在开辟新的数组,遍历元素组元素,小于p的从头插入数组,大于p的从尾部插入数组,给个例子:
而第二种方法是原地排序,比第一稍微复杂一点。假设p元素总在数组的最前端(不在最前端就让它和最前端的元素交换),将整个数组分为两部分,前半部分为已经和p比较过的元素集,后半部分为没有和p比较过的元素集。其中前半部分又分为小于p和大于p两部分。如图:
那么只要需要两个标记值i和j就可以所有部分分割开。i 标记小于p部分末端元素,j标记大于p部分的末端元素。如下例:
分割(Partition)的伪代码:
Partition(A, l, r) [input=A[l.......r]] p=A[l] i=l+1 for j=l+1 to r if A[j]<p swap A[i] and A[j] i=i+1 swap A[l] and A[i-1]
假设处理的数组长度为N,从伪代码中可以比较容易算出,Partition的时间复杂度为O(N),而且也实现了原地排序。
Python代码
Pivot选取首元素的实现
1 import random 2 3 def quick_sort(datalist,l,r): 4 if l<r-1: 5 q=partition_first(datalist,l,r) 6 datalist=quick_sort(datalist,l,q) 7 datalist=quick_sort(datalist,q+1,r) 8 return datalist 9 else: 10 return datalist 11 12 def partition_first(datalist,l,r): 13 p=datalist[l] 14 i=l+1 15 for j in range(l+1,r): 16 if datalist[j]<p: 17 datalist[i],datalist[j]=datalist[j],datalist[i] 18 i=i+1 19 datalist[l],datalist[i-1]=datalist[i-1],datalist[l] 20 return i-1