快速排序
快排同归并排序一样,也利用了分治(Divide and Conquer)的思想。此外,快排还能够在原址进行排序(最多只需要常数个临时存储空间)。
一个快速排序包括一下三个步骤:
- 分解:将数组A[1…n]划分为两个(可能为空)子数组A[1…p-1]和A[p+1…n],使得A[1…p-1]中的所有元素都不大于A[p],A[p+1…n]中的所有元素都不小于A[p]。这里,下标p的计算是分解过程中的重要步骤。
- 递归:通过递归调用快排,对子数组A[1…p-1]和A[p+1…n]进行排序。
- 合并:由于子数组都是原址进行排序的,所以不需要合并操作。数组A[1…n]已经有序。
下面是数组的划分过程:
int partition(int A[], int left, int right)
{
int temp = A[left];
while(left < right)
{
while(left < right && A[right] >= temp)
right--;
if(left < right)
{
A[left] = A[right];
left++;
}
while(left < right && A[left] < temp)
left ++;
if(left < right)
{
A[right] = A[left];
right--;
}
}
A[left] = temp;
return right;
}
有了划分的过程的代码,分治部分就好写了:
void QuickSort(int A[], int left, int right)
{
int position;
if(left < right)
{
position = partition(A, left, right);
QuickSort(A, left, position - 1);
QuickSort(A, position + 1, right);
}
}
快排的运行时间取决于划分元素的选取,上面例子选取第一个数作为划分,当然,也可以选最后一个。如果元素选取平衡的话,快排的时间复杂度
同归并排序一样,同为O(n*logn);若选取元素不平衡,则效率接近插入排序。
《算法导论》中讲到任何一种常数比例的划分都会产生深度为θ(lg n)的递归树,其中每一层的代价都是O(n)。
因此。只要划分是常数比例的,快排的运行时间总是O(n*logn)。