Partition函数实现的快速排序和寻找第k大数
快速排序的核心内容其实就是Partition函数,以前一直没有什么深入的理解,也只是背一下快排的代码,再后来干脆直接用sort了。但是了解Partition函数对于理解快排非常重要,还可以实现别的应用。
Partition函数的功能:对于一个你选定的数,将小于这个数的元素放置到数组的一边,大于这个数的元素放置到数组的另一边,最后你可以知道这个选定的数在数组中是第几大的。
下面给出一个样例的Partition函数:
int Partition(int A[], int Left, int Right){
int Pivot = A[Left] ;
int L = Left, R = Right ;
while( L < R ){
while( A[R] < Pivot && L < R ) R-- ;
while( A[L] >= Pivot && L < R ) L++ ;
Swap( A[L], A[R] ) ;
}
Swap( A[Left], A[R] ) ;
return L ;
}
可以看到,这个Partition函数选择了最左边的元素作为主元(比较的标杆),把小于等于主元的数放到了右边,大于等于主元的数放到了左边。注意,这里有一个小陷阱:因为最后要进行Swap( A[Left], A[R] ),这意味着最后的A[R]一定是一个大于等于主元的数。所以,我们把R的while循环写到前面。如果循环因为A[R]>Pivot而停止,那么是合法的;如果因为R==L而停止,可以想一下,交换过后A[L]总是大于等于Pivot,所以也是合法的。
这样的话,一次Partition就把数组分成了两个部分。这样再递归处理左右两边就可以完成快速排序了。
PivotPivotPivot 的选择是很关键的,决定了你划分的好坏。一般的方法是选择左,中,右三个元素的中间值。
void Quicksort(int A[], int Left, int Right ){
if( Left >= Right ) return ;
int index = Partition(A, Left, Right ) ;
Quicksort(A, Left, index-1 ) ;
Quicksort(A, index+1, Right ) ;
}
即使不谈快速排序,Partition函数依然很有用:可以寻找乱序数组中的第k大数。能不能想到怎么做呢?
前面说过,Partition函数可以返回主元在数组段中的大小位置。如果我们要寻找的元素是第k大的,而当前的Partition返回的值是m(按照给出样例中的Partition函数讨论),则:
- k == m 恭喜你,这个元素正是你需要的!
- k < m 说明要找的数比这个数大,也就是说,在这个数的左边
- k > m 说明要找的数比这个数小,也就是说,在这个数的右边
如此一来,问题就被一次Partition函数分解了。只需要向下递归几次,就可以找到想要的数。
int findk(int A[], int Left, int Right, int k ) {
int index = Partition(A, Left, Right ) ;
if( index == k-1 ) return A[index] ;
else if( index < k-1 ) return findk(A, index+1, Right, k) ;
else return findk(A, Left, index-1, k) ;
}
由于其递归的特性,代码写起来也比较简洁明了。