面试中一个频率极高的问题就是要手写快速排序,实际上涉及下标变换的代码极其容易写错,基本上涉及整数下标操作的问题都算是难度中等偏上的了。
对于一些经典问题,我们要时不时的去练习,这里给出算法导论上的和STL中实现的快速排序的两种不同写法。
算法导论中的写法极其简单,在面试中十分推荐写这种写法:
template <typename T>
size_t partition(T *a, size_t p, size_t q)
{
size_t r = q; // pivot
for (size_t i = p; i < q; i++)
if (a[i] > a[r])
swap(a[i--], a[--q]);
swap(a[q], a[r]);
return q;
}
template <typename T>
void quick_sort(T *a, size_t n)
{
if (n < 2) return;
size_t m = partition<T>(a, 0, n - 1);
quick_sort(a, m);
quick_sort(a + m + 1, n - m - 1);
}
算法的关键是partiton函数,该函数将数组a[p..q]中的最后一个元素作为分割标准pivot,从左到右依次与pivot比较,如果a[i]小于等于pivot则按兵不动,如果a[i]大于pivot则将a[i]与数组的倒数第二个交换,注意swap(a[i--], q[--q]);这行的细节:q初始为数组最后一个元素的下标,交换时它先指向倒数第二个元素,再交换,也就是说q始终保持指向未划分完成的区域的最后一个指针的下一个指针,即左闭右开区间[p, q)始终表示当前未划分的元素的下标。而i--的保证了交换元素后下一次循环依旧使用当前位置的元素与pivot进行比较,因为刚刚交换过来的元素还未与pivot比较过。
for循环的终止条件判定为i<q,这是说明指针要遍历尚未划分元素的区间[p,q)内的每一个元素。
当for循环终止时,q之前的所有元素a[i]均满足a[i]<=pivot,而q及q之后的元素[q,r)则满足a[q]>pivot。这时将a[r]与a[q]进行交换就刚好满足a[q]位置的左侧不大于它,a[q]的右侧比它大。
再来参考STL中的partition写法,为了简便使用了整数。
// [p, q)
int partition(int *a, int p, int q, int pivot)
{
while (p != q) {
while (a[p] <= pivot) {
++p;
if (p == q) return p;
}
do
{
--q;
if (p == q) return p;
} while (a[q] > pivot);
swap(a[p], a[q]);
++p;
}
return p;
}
其快排的写法应为:
void quick_sort(int *a, int n)
{
if (n < 2) return;
//int m = partition(a, 0, n - 1);
int m = partition(a, 0, n, a[0]);
swap(a[0], a[m-1]);
quick_sort(a, m - 1);
quick_sort(a + m, n - m);
}