淺入淺出數據結構(22)——排序決策樹與桶式排序

  在(17)中咱們對排序算法進行了簡單的分析,並得出了兩個結論:算法

  1.只進行相鄰元素交換的排序算法時間複雜度爲O(N2)數組

  2.要想時間複雜度低於O(N2),排序算法必須進行遠距離的元素交換函數

  

  而今天,咱們將對排序算法進行進一步的分析,這一次的分析將針對「使用比較進行排序」的排序算法,到目前爲止咱們所討論過的全部排序算法都在此範疇內。所謂「使用比較進行排序」,就是指這個算法實現排序靠的就是讓元素互相比較,好比插入排序的元素與前一個元素比較,若反序則交換位置,再好比快速排序小於樞紐的元素分爲一組,大於樞紐的元素分爲另外一組。它們都是依靠「比較」來完成排序工做。spa

  要對使用比較進行排序的算法進行分析,咱們首先要引入一個概念:決策樹。code

  決策樹就是這樣的二叉樹:樹的根結點表示「元素的全部可能順序」,樹的每一條邊表示「一種可能的結果」,一條邊鏈接的孩子結點則是「父結點通過該邊所表明的比較結果後剩餘的可能順序」。這樣的解釋很難理解,但有圖搭配就能夠好不少:blog

  

  上圖是一棵三元素排序決策樹,根結點處表示全部可能的順序,而從根延伸下來的兩條邊分別表示了兩種「決策結果」,或者說「比較結果」,若符合該「決策結果」就能夠得出剩餘的可能狀況,好比根結點的左孩子是經歷決策「a<b」後剩餘的可能。顯然,葉子表明只剩一種可能順序。排序

 

  注意,決策樹並無表明任何排序算法,即沒有哪一個排序算法是這樣工做的。可是決策樹能夠給咱們這樣一個信息:經過比較來排序的算法,本質上就是沿着該元素集合的決策樹從根到某個葉子的路徑比較下去。class

  所以,分析這條「路徑」平均通過多少條邊,就至關於分析使用比較的排序算法平均須要多少次比較。這也是本次分析與(17)的不一樣之處,在(17)中咱們的分析針對的是排序算法的「交換」次數,此次咱們分析的是「比較」次數,而比較次數顯然更爲特殊,由於不論元素是否遠距離交換,比較老是存在的。二叉樹

 

  要分析使用比較進行排序的算法平均進行幾回比較,咱們就必須知曉如下定理。搜索

  定理1:深度爲d的二叉樹,最多擁有2d個葉子

  證實很簡單:二叉樹的深度d即二叉樹中深度最大的葉子的深度d,若存在某個葉子深度不是d,則能夠在該葉子下添加兩個孩子而不改變樹的深度,所以深度爲d的二叉樹要有最多的葉子則必爲滿二叉樹,此時有葉子2d個(深度爲d的層最多有2d個結點)

 

  定理2:有Y個葉子的二叉樹,深度至少爲[logY](底數默認爲2)

  證實:由定理1能夠直接推出。

  這個證實可能有點難懂,咱們能夠舉一反三一下:假如1元錢最多能夠買5個糖,那麼5個糖最少須要多少錢?答案是1元,剛好是反函數的關係。相似的,深度爲x的二叉樹最多有y個葉子,那麼有y個葉子的二叉樹最少有多少深度?答案就是x了。

 

  定理3:N元素排序的決策樹有N!個葉子結點

  證實:N元素排序的可能順序共有N!個,而決策樹的葉子就是表示「僅剩的可能性」即某一種可能順序,因此N元素排序的決策樹共有N!個葉子

 

  定理4:使用元素比較的排序算法至少須要O(logN!)次比較

  證實:由定理2可知,有y個葉子的決策樹,深度至少爲[logy],而N元素排序決策樹葉子數量必爲N!,因此N元素排序決策樹深度至少爲[logN!],也即N元素排序決策樹的任一葉子深度至少爲[logN!],而葉子的深度就表示了從根到該葉子的路徑上通過的邊的數量,也就是「比較」的次數,所以定理4成立。

 

  定理5:使用元素比較的排序算法至少須要Ω(N*logN)次比較

  證實:根據定理4進行繼續計算:

  logN!=log(N*(N-1)*(N-2)*……*2*1)

    =logN+log(N-1)+log(N-1)+……+log2+log1

    >=logN+log(N-1)+……log(N/2)

    >=(N/2)*log(N/2)=(N/2)*log(N*1/2)=(N/2)*logN+(N/2)*log(1/2)

    >=(N/2)*logN-N/2

    =Ω(N*logN)

  

  定理5就是咱們此次分析的最終結果,而且咱們能夠將定理5進行一個推廣:假設存在X種可能情形,肯定具體情形的方法是不斷地問「是或否」型的問題,那麼累計須要問的次數至少是[logX]。

 

  那麼根據定理5,堆排序、合併排序和快速排序是否已經表明了排序的最快境界呢?不是的,由於定理5依然是有「限定」的,那就是經過比較進行排序的算法才符合,也就是說不是經過比較來完成排序的話,是可能突破這個界限的。

  不經過比較來完成排序,是個什麼樣子?咱們這裏能夠舉一個簡單的例子:桶式排序。其時間複雜度是O(N)。

  現實生活中桶式排序的思想是很多見的,舉個例子感覺一下:

  假設咱們有不少硬幣,一分、二分、五分、一角、五角和一元都有,如今咱們想要將它們按從小到大排好序,該怎麼作?手工模擬任意排序算法均可以完成這項工做,但沒有人會這麼傻。大部分人的作法都是:準備6個「桶」,分別存放這6種硬幣,一分的扔進一分桶,一元的扔進一元桶,全部硬幣扔進桶裏了,再按順序從桶裏倒出來,排序就完成了。

 

  將上述思想轉換到計算機中就是這樣:假設咱們的元素都是天然數,且必定小於MAX,那咱們只要準備MAX個空桶,即定義一個整形數組bucket[MAX],並將其所有初始化爲0。而後遍歷全部元素,若元素爲i,則令bucket[i]加1,最後統計數組bucket的狀況,就能夠得出元素的順序:

//size爲數組src的大小,也即元素個數
void BucketSort(unsigned int *src,unsigned int size)
{
    //MAX爲宏,表示src中元素不會大於等於的值
    unsigned int bucket[MAX] = { 0 };
    
    //將元素們「扔進桶裏」
    for (unsigned int i = 0;i < size;++i)
        ++bucket[src[i]];

    //將桶裏的元素「倒出來」
    unsigned int j = 0;  
    for (unsigned int i = 0;i < MAX;++i)
        for (unsigned int x = 0;x < bucket[i];++x)
            src[j++] = bucket[i];
}

  顯然,桶式排序的侷限性在於要求元素必須是天然數,必須存在上限且上限不可過度大,由於元素的上限決定了桶的數量,而桶的數量並非想要多少有多少,好比個人電腦就不支持分配一個大小爲INT_MAX的數組。

  桶式排序還有一種變種,只須要10個桶便可,感興趣的能夠去搜索「桶式排序」或「基數排序」,此處不作介紹。

 

  本篇博文就是有關排序的最後一篇博文了,下一篇博文開始,我將會介紹圖論算法,並不難,至少理解起來是不難(實現起來就難說了)。

相關文章
相關標籤/搜索