[算法導論]快速排序學習

基礎

看懂《算法導論》需要重溫數學知識。算法實現和推導是一個數學建模過程。


原理

對於包含 n 個數的輸入數組來說,快速排序是一種最壞情況時間複雜度爲 O(n2n^2) 的排序算法。雖然最壞情況的時間複雜度很差,但是快排通常是實際排序應用中最好的選擇,因爲它平均性能非常好,它的期望實際複雜度是O(nlgnnlgn),而且O(nlgnnlgn)中隱含的常數因子非常小,另外它還能夠進行原址排序,甚至在虛存環境中也能很好地工作。

詳細內容請參考《算法導論》第三版,第二部分,第七章:快速排序


實現

根據數組哨兵的選擇,有兩種遞歸方式實現。調試代碼在 (github)

  • 以數組末位數值爲哨兵,從左向右排序
int Partition(int array[], int start, int end) {
    int low = start - 1;
    int high = low + 1;
    int key = array[end];

    for (; high < end; high++) {
        if (array[high] <= key) {
            low++;
            if (high > low) {
                int temp = array[low];
                array[low] = array[high];
                array[high] = temp;
            }
        }
    }

    // 如果是有序數組,會出現左邊都是最小的情況,要置換 partition 需要判斷數據。
    int partition = low + 1;
    if (array[partition] > key) {
        int temp = array[partition];
        array[partition] = array[end];
        array[end] = temp;
    }

    return partition;
}

void qsort_end(int array[], int start, int end) {
    if (start < 0 || end <=0 || start >= end) {
        return;
    }

    int partition = Partition(array, start, end);
    if (partition >= 0) {
        qsort_end(array, start, partition - 1);
        qsort_end(array, partition + 1, end);
    }
}
  • 以數組中間數值爲哨兵,從兩端向中間排序
void qsort_mid(int array[], int start, int end) {
    if (start >= end) {
        return;
    }

    int high = end;
    int low = start;
    int key = array[(unsigned int)(start + end) / 2];

    while (low < high) {
        // 左邊向右查找比 key 大的
        while (array[low] < key && low < end) {
            low++;
        }

        // 右邊向左查找比 key 小的
        while (array[high] > key && high > start) {
            high--;
        }

        if (low <= high) {
            int temp = array[low];
            array[low] = array[high];
            array[high] = temp;
            low++;
            high--;
        }
    }

    qsort_mid(array, start, high);
    qsort_mid(array, low, end);
}

時間複雜度推導

最優情況下的時間複雜度

快速排序涉及到遞歸調用, 遞歸算法的時間複雜度公式:
T[n]=aT[nb]+f(n)T[n]=aT[\frac{n}{b}] + f(n)
數組共有 nn個數值,最優的情況是每次取到的元素(哨兵)剛好平分整個數組。
此時的時間複雜度公式爲:T(n)=2T[n2]+f(n)T(n)= 2T[\frac{n}{2}] + f(n)


第一次遞歸:
T(n)=2T[n2]+f(n)T(n)= 2T[\frac{n}{2}] + f(n)


第二次遞歸:令 n=n2n = \frac{n}{2} ,
T[n2]=2{2T[n4]+(n2)}+n=22T[n(22)]+2nT[\frac{n}{2}] = 2 \{2T[\frac{n}{4}] + (\frac{n}{2})\} + n = 2^2T[\frac{n}{(2^2)}] + 2n


第三次遞歸:令 n=n(22)n = \frac{n}{(2^2)}
T[n22]=22{2T[n23]+n22}+2n=23T[n23]+3nT[\frac{n}{2^2}] = 2^2\{2T[\frac{n}{2^3}] + \frac{n}{2^2}\}+2n = 2^3T[\frac{n}{2^3}]+3n


mm次遞歸:令 n=n2(m1)n = \frac{n}{2^{\left (m-1) \right.}}
T[n2m1]=2mT[1]+mnT[\frac{n}{2^{m-1}}] = 2^mT[1]+mn


公式一直往下迭代,當最後數組不能再平分時,最後到T[1]T[1],說明公式迭代完成(T[1]T[1]是常量)也就是: n2(m1)=1\frac{n}{2^{\left (m-1) \right.}} = 1

n=2(m1)n = 2^{\left (m-1) \right.} ==> ( n=2mn = 2^m ) ==> ( m=log2nm = log_2n )

m=log2nm = log_2n

T[n2(m1)]=2mT[1]+mn=n+nlog2nT[\frac{n}{2^{\left(m-1)\right.}}] = 2^mT[1]+mn = n + nlog_2n

nn 爲元素個數,當 n2n \geq 2

n+nlog2n=n(1+log2n)==>nlog2n==>nlgnn + nlog_2n = n(1+log_2n) ==> nlog_2n ==> nlgn


參考

算法導論 時間複雜度分析
快速排序 及其時間複雜度和空間複雜度
算法導論------遞歸算法的時間複雜度求解
算法複雜度中的O(logN)底數是多少
Cmd Markdown 公式指導手冊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章