建堆複雜度O(n)證實

堆排序中首先須要作的就是建堆,廣爲人知的是建堆複雜度才O(n),它的證實過程涉及到高等數學中的級數或者機率論,不過證實總體來說是比較易懂的。算法

堆排過程

代碼以下api

void print(vector<int> &arr)
{
    for(auto n: arr) printf("%d\t", n);
    cout<<endl;
}

// 以arr[n]爲根的子樹,將arr[n]向下調整至合適位置
void Heapify(vector<int> &arr, int size, int n)
{
    int L = n*2+1, R = L+1;
    if(L>=size) return ;//無孩

    int big = arr[L];         // 取兩孩之大者
    if(R<size) big = max(big, arr[R]);

    if(arr[n]>=big) return ;  //無需調整

    int c = L;      // 欲與父交換位置的孩子
    if(big!=arr[L]) c = R;
    swap(arr[n], arr[c]);
    Heapify(arr, size, c);
}

// 小根堆
void BuildHeap(vector<int> &arr)
{
    int last = (arr.size()-1)/2;
    for(int i=last; i>=0; i--) {
        Heapify(arr, arr.size(), i);
    }
}
// 順便排序
void Sort(vector<int> &arr)
{
    int size = arr.size();
    for(int i=size-1; i>0; i--) {
        swap(arr[0], arr[i]);
        Heapify(arr, i, 0); //調整一下arr[0]
    }
}

int main()
{
    vector<int> vect{9, 10, 6, 3, 1, 6, 2, 8, 4};
    print(vect); //排序前
    BuildHeap(vect); //建堆
    Sort(vect);  //排序
    print(vect); //排序後
    return 0;
}

建堆的過程就是從最後一個分支結點開始逐層向上遍歷,將結點向下調整至合適的位置,以不至於破壞原來的堆。好比上圖,遍歷的結點編號依次爲3 2 1,首先調整以3爲根的子樹成堆,其次是以2爲根的子樹成堆,最後是以1爲根的子樹成堆。至此建堆完成,複雜度O(n)。ui

注意:建堆不能寫成以下這樣,這樣的建堆算法複雜度是O(nlogn),雖然不會影響堆排序的複雜度O(nlogn),可是實現其餘算法時就很不利了。spa

// 將arr[n]向上調整至合適位置
void AdjustHeap(vector<int> &arr, int n)
{
    if(n<=0) return ;
    if(arr[(n-1)/2] > arr[n]) {  //與父結點比較
        swap(arr[(n-1)/2], arr[n]);
        AdjustHeap(arr, (n-1)/2);   //遞歸調整
    }
}
// 小根堆
void BuildHeap(vector<int> &arr)
{
    for(int i=1; i<arr.size(); i++) {
        AdjustHeap(arr, i);
    }
}

複雜度計算

從直觀上看,Heapify()的遞歸深度最多爲${log_n}$,故它的複雜度上限爲O(logn)。而BuildHeap()中的循環爲${ \frac{n}{2} }$次,故它的複雜度爲O(nlogn),但這不是它的實際複雜度,而是一個估算的上界,它極可能永遠達不到這個上界。爲了方便計算,考慮結點數量爲n,高度爲h的滿二叉樹,所以${2^h-1 = n}$,即${h = log_2{(n+1)}}$。code

第幾層 最多調整次數 層調整次數累計
${h}$ $0$ ${2^{h-1}*0}$
${h-1}$ $1$ ${2^{h-2}*1}$
${h-2}$ $2$ ${2^{h-3}*2}$
$\vdots$ $\vdots$ $\vdots$
$3$ ${ h-3 }$ ${2^{2}*(h-3)}$
$2$ ${ h-2 }$ ${2^{1}*(h-2)}$
$1$ ${ h-1 }$ ${2^{0}*(h-1)}$

將最右邊一列累加起來就是建堆的調整次數,則建堆的調整次數${S(n)}$爲排序

$${S(n) = 12^{h-2}+22^{h-3}+}\cdots {+(h-2)*2^1 +(h-1)*2^0}$$ $${=2^{h-1} * ( \frac{1}{2^{1}} +\frac{2}{2^{2}} +\frac{3}{2^{3}} +}\cdots {+\frac{h-2}{2^{h-2}} +\frac{h-1}{2^{h-1}} )} \tag{1}$$遞歸

則 $${\frac{1}{2} S(n) = 2^{h-1} *(\frac{1}{2^2} + \frac{2}{2^3} + \frac{3}{2^4} + }\cdots{+\frac{h-2}{2^{h-1}} +\frac{h-1}{2^h})} \tag{2}$$數學

將(1)式減去(2)式得 $${S(n)-\frac{1}{2}S(n) = 2^{h-1} * (\frac{1}{2^1} + \frac{1}{2^2} + \frac{1}{2^3} + }\cdots{+\frac{1}{2^{h-2}} + \frac{1}{2^{h-1}} -\frac{h-1}{2^h} )} $$ $${ = 2^{h-1} * (\frac{1}{1-\frac{1}{2}}-1-\frac{h-1}{2^h} ) } \tag{3}$$ $${ =2^{h-1} * ( 1 - \frac{h-1}{2^h})}$$ $${ =2^{h-1}} $$table

又因 ${ n = 2^h-1 }$,故有 $${S(n) = 2^h = \frac{n+1}{2}}$$ast

注意:上面列式均是當n趨於無窮大時的計算,且(3)式是由級數的直接變換所得。其餘的證實思路還有用機率的,就不寫了。

寫公式寫到頭皮發麻,寫錯n次了,若是錯漏請不吝指正,感謝!

相關文章
相關標籤/搜索