面試官,您要的快排

pezy發表於2019-05-10

今天看到 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 選取的改變,基於上述程式碼,改造起來是非常容易的。這裡就不做過多實現,留作感興趣者自己練習吧。


上述三道菜,基本能夠解決面試中可能遇到的種種情況了。讓面試官吃飽,很有必要~

相關文章