今天看到 V2EX 上有人讨论 社招还会问 “请手写选择排序算法” 吗?,看来还是有很多人关心的。结合自己最近面试的经历,我可以明确的告诉大家,类似这种问题,只要你的工作经验小于 10 年,基本上逃不掉。劝大家不如抽点时间早做准备。
简式快排
面试中遇到问快排的,如上面那个帖子中的情况。你就可以上一份简式快排了,何谓简式?最短的代码表述快排的思想。
快排的思想,实质是分治法。基于什么来分?找一个支点来分,通常称之为 pivot, 而这个分的过程称之为 partition, 基于以上两点,我们用递归的方式描述快排:
void quicksort(int arr[], int l, int r) {
if (l < r) {
int pivot = partition(arr, l, r);
quicksort(arr, l, pivot-1);
quicksort(arr, pivot+1, r);
}
}
如何?简单吧。有人说面试的时候手写快排,如果提前没有背下来的话,肯定歇菜。我不认为这样基础的算法是需要背的,上面这个递归,如此简洁,如此美,真的需要硬记?
有人说,这个好理解,关键在于 partition
如何实现。的确,partition
是快排的灵魂。CLRS 里采用了以尾巴为支点的策略,我在这里与其保持一致:
int partition(int arr[], int l, int r) {
int k = l, pivot = arr[r];
for (int i = l; i < r; ++i)
if (arr[i] <= pivot) std::swap(arr[i], arr[k++]);
std::swap(arr[k], arr[r]);
return k;
}
这算法用白话说,就是从头到尾迭代,和支点比较,大的不管,小的换。换了才往后看。最后支点戳中间,算是分界线。
如
3,7,8,5,2,1,9,5,4
这么来一下,就成了:
3,2,1,4,7,8,9,5,5
^^^^^ | ^^^^^^^^^
然后同样的手法分别解决两边,这样递归的解下去。
来份三明治
其实上面的那份,已经可以解决面试中的问题了。但其实有很大的缺陷,如当所有元素都相同的情况下,partition
将一直返回 r
, 递归的深度高达 N
,每一次递归中 partition
又循环 N
次,时间复杂度直接飙到了 O(N^2). 这显然非常的不值当。
于是我们觉得分两份太粗,分三份试试?
如
5 7 4 3 1 2 6 5 5
也来那么一下,成为:
4 3 1 2 5 5 5 7 6
^^^^^^^ | | ^^^
这就是三明治的原理了,左边是小于 pivot 的,中间是等于 pivot 的,右边是大于 pivot 的。中间部分不参与递归,分治的是两边。
我们需稍稍改变下 partition
的实现,显然我们这次希望返回的两个支点(左右边界):
// 3-way partition
std::pair<int, int> partition(int arr[], int l, int r) {
int k = l, p = r;
for (int i = l; i < p; )
if (arr[i] < arr[r]) std::swap(arr[i++], arr[k++]);
else if (arr[i] == arr[r]) std::swap(arr[i], arr[--p]);
else ++i;
// move pivots to centre
int m = std::min(p-k, r-p+1);
for (int i = 0; i < m; ++i)
std::swap(arr[k+i], arr[r-i]);
return std::make_pair(k, r-p+k);
}
而 quicksort
也需要稍稍改变一点:
void quicksort(int arr[], int l, int r) {
if (l < r) {
auto pivot = partition(arr, l, r);
quicksort(arr, l, pivot.first-1);
quicksort(arr, pivot.second+1, r);
}
}
如果在遇到全部元素相同的情况,时间复杂度成功的减少到 O(N). 算是一个突破吧。
东北乱炖
其实 CLRS 随后还提到了以随机位置为 pivot 的思路,我称之为东北乱炖。那是只针对 pivot 选取的改变,基于上述代码,改造起来是非常容易的。这里就不做过多实现,留作感兴趣者自己练习吧。
上述三道菜,基本能够解决面试中可能遇到的种种情况了。让面试官吃饱,很有必要~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。