算法导论笔记 第7章 快速排序
快速排序是用于排序的最佳使用选择,这是因为其平均性能相当好,期望运行时间是O(nlgn),且记号中隐藏的常数因子很小。另外:它还能够进行就地排序。
快速排序是基于分治模型的:
- 分解:数组A[p..r)被划分成两个(可能为空)子数组A[p..q)和A[q+1..r),使得A[p..q)中的每个元素都小于A[q], 而且,小于等于A[q+1..r)中的元素。下标q也在这个划分过程中进行计算。
- 解决:通过递归调用快排程序,对子数组A[p.q)和A[q+1..r)进行排序。
- 合并:因为两个子数组是就地排序的,将它们合并不需要操作:整个A[p..r)已经排序。
void quicksort(int* A, int p, int r){ if (p < r) { int q = partition(A, p, r); quicksort(A, p, q); quicksort(A, q+1, r); } }
为排序一个完整的数组A,最初的调用是quicksort(A, 0, __count_of(A) )
快排算法的关键是PARTITION过程,它对子数组A[p...r)进行就地重排
int partition(int* A, int p, int r) { int x = A[r -1]; int i = p - 1; int j = p; for (; j < r-1; j++) { if (A[j] <=x) { i++; int tmp = A[i]; A[i] = A[j]; A[j] = tmp;// A[i] A[j] } } i++; int tmp = A[i]; A[i] = A[j]; A[j] = tmp;// A[i] A[j] return i; }
可以用循环不变式来分析一下Partition程序:
循环不变量是.任何在[p,i]的数都小于等于x, 在[i,j)的数都大于x。 在[j,r)的数未知。 A[r-1] = X。
- 初始化:在循环的第一轮迭代开始之前,i = p-1, j=p。 在p与i之间没有值。在i+1与和j-1之间也没有值,且x = A[r -1] 因此满足循环不变式。
- 保持:当A[j]>x时,循环唯一的操作是j++. j自加后满足在[i,j)的数都大于x。且其它所有项保持不变。 当A[j]<=x时,自加i, 然后交换A[i]和A[j]。因为进行了交换,所以有A[i] <=x,因此满足[p,i]的数都小于等于x。并且A[j-1] > x因为根据循环不变式,被交换进A[j-1]的项目是大于x的。
- 终止: 当终止时, j=r-1。在partition过冲中的而最后两行,将主元与最左的,大于x的元素进行交换,就将它移动到数组中间的位置上。因此partition的输出满足分解步骤所做的规定要求。
快排性能最差时是Θ(n2)
平均性能Θ(nlgn)
快排的随机化版本
int randomized-partition(int* A, int p, int r) { int i = random(p,r); int tmp = A[r-1]; A[r-1] = A[i]; A[i] = tmp; return partition(A, p, r); }
新的快速排序过程不再调用partition,而是调用randomized-partition
void quicksort(int* A, int p, int r){ if (p < r) { int q = randomized-partition(A, p, r); quicksort(A, p, q); quicksort(A, q+1, r); } }
扩展:
快排除了算法导论的版本外,c语言的库函数stdlib.h也提供了一个qsort函数。它的源代码可以从这里下载http://www.gnu.org/software/libc/
代码路径在glibc-2.27/stdlib/qsort.c. 不再累述。
浙公网安备 33010602011771号