MIT算法導論——第五講.Linear Time Sort

本欄目(Algorithms)下MIT算法導論專題是我的對網易公開課MIT算法導論的學習心得與筆記。全部內容均來自MIT公開課Introduction to Algorithms中Charles E. Leiserson和Erik Demaine老師的講解。(http://v.163.com/special/opencourse/algorithms.htmlhtml

第五節-------線性時間排序 Linear Time Sort算法

這節課的主要內容是分析基於比較的排序可以達到的最快效率以及介紹幾種非比較的線性時間排序算法。數組

1、排序可以達到的最快效率緩存

目前已經接觸了好幾種排序的算法,那麼有個疑問:排序最快可以達到多快的速度?Θ(n^2),只容許進行相鄰數字交換時這個答案正確;Θ(nlgn),這個答案一般是正確的;Θ(n),這個答案也只有在特定狀況下也是正確的。因此正確的回答應該是看狀況,它取決於你使用的計算模型裏,哪些操做是被容許的。在這裏主要指的是在排序過程當中,哪些操做是被容許的。函數

目前接觸過的算法(插入排序,歸併排序,快速排序,堆排序),都是基於比較的排序算法。在比較排序算法模型中,你只能作比較操做來決定元素的相對順序,也就是咱們不能進行整數相乘或者其餘奇怪的操做。咱們見到過對於基於比較的排序算法來講,最壞狀況下的時間複雜度最快的是Θ(nlgn)。那麼就有一個疑問,咱們可以作的比Θ(nlgn)更快麼?決策樹可以幫咱們回答這個問題。學習

如上圖所示,是對三個元素的序列進行比較排序的決策樹分析。其中圖中的內部節點標記爲i : j的形式表示序列中第i個元素與第j個元素比較;左子樹表示若是a[i] <= a[j]的後續比較狀況,右子樹則表示若是a[i] >= a[j]的後續比較狀況;葉子節點即表示從根節點到葉子節點路徑所肯定的序列的排列順序。htm

這就是比較排序的決策樹模型。雖然對於一種排序算法畫出它的通用的決策樹比較困難(決策樹的複雜程度是n的指數級),可是咱們能夠認爲任何一種比較排序算法都有其相應的決策樹,也就是能夠利用決策樹來分析算法複雜度狀況。算法的運行時間:根節點到葉子節點的路徑長度;最壞狀況下運行時間:決策樹的高度。排序

下面咱們利用決策樹分析來證實:任何排序n個元素的決策樹的高度是Ω(nlgn)。ci

由上述證實過程能夠知道,決策樹的下界問題,也稱爲基於比較的排序算法的下界問題。具體來講,它說明了歸併排序和堆排序算法是漸進最優的。隨機化快速排序算法在理想狀況下也是漸進最優的。get

2、線性時間排序

下面講的是對比較模型的一個突破,咱們要儘可能在線性時間內完成排序。咱們知道,若是不用並行算法,或者什麼超能力,你不可能比線性時間更快完成排序,由於你得去遍歷這些數據。不管你作什麼,你必須遍歷,不然你沒法對其正確排序。因此線性時間使咱們指望的最好結果。那麼咱們如何在線性時間內排序?咱們須要一些更有效的設想。下面看兩種比nlgn快的排序算法。

1. 計數排序

對於計數排序,咱們將不用比較的方式,而是對元素作一些其餘的操做。咱們要作的是假設輸入序列是在特定範圍內的整數。咱們會利用這點來在線性時間內排序。下面是計數排序的僞代碼描述。

僞代碼看起來確實比較費解,下面經過一個例子,分別看看這四個for循環作的事情。第1個for循環簡單,初始化將C置0。第2個for循環遍歷輸入序列A,計算A中等於C對應位置的個數來填充輔助數組C,獲得結果:

第3個for循環,作的操做即是計算C中當前位置以前全部數字之和並填充C,獲得結果:

第4個for循環,從後往前遍歷A中數據,根據C中計算結果,將A中數據放在B中正確的位置。例如A中第一個元素4,則C[4]即爲元素4在B中所處的正確位置,即4,也就是目前B中空缺的位置。以下圖所示,這樣就將A排好序放在了B中。

那好,計算排序算法的算法效率呢?由僞代碼能夠容易知道,複雜度爲Θ(n + k)。若是k = O(n)這極好,咱們就獲得了線性時間的排序算法。因此看來,咱們不只須要假設這些數是整數,還須要確保這些整數的範圍很小。可是從另外一方面想,只要k小於nlgn咱們就該滿意了。因此能夠寫一個混合算法,若是k比nlgn大咱們就用歸併排序,若是它比nlgn小就用計數排序。

對於計數排序某個實用的場景:若是你在處理的數字長度爲1字節,這就很好。k僅爲2^8,這就是256,你須要的輔助數列長度爲256,這很快O(256 + n),不管n多大這都是n的線性時間。因此若是你知道數字很小這個算法很好,可是若是數字更大,即便你知道它們還是整數,但它們是相似於32位的雙字,就不是那麼簡單了。由於這時須要的輔助空間就是2^32,16G。

另外,計數排序是一種穩定排序:對於相等的元素,它保持了他們在輸入數列中的順序。

2. 基數排序

接下來咱們要接觸一個更有趣的算法,這個算法會利用計數排序做爲小數據規模的子程序,結合這個算法那去處理更大規模的數字。

基數排序極可能是最古老實現的算法了,大約出如今1890年,Herman Hollerith應用於卡片分類機器(card-sorting machine)。Herman Hollerith也曾今是MIT的講師,他發明了最先版本的打孔卡片。基數排序首先對最後一位進行排序,這裏就須要用到一種穩定排序算法,而後在對高位進行排序。這個算法讓Hollerith賺了不少錢,他在1911年發明了這個製表機,而後在1924年吞併了幾個公司組成了一個叫作IBM的新公司,這個多是你據說過Hollerith的緣由。下面就是基數排序的一個例子。

關於這個算法的正確性的證實,咱們假設一組數字在他們的低t - 1位已經排好序,那麼如今排序第t位。在t位上,對於兩個數字不相等的可以正確決定他們的順序;對於兩個數字相等的,基於穩定性原則,可知它們保持原順序,因此也是正確的。而後根據概括,咱們能夠知道它們仍然保持有序的。

接下來分析基數排序的算法複雜度。假設將計數排序做爲輔助的穩定排序算法。可是我不想按照每個數字位來進行依次排序,那樣將會失去太多靈活性。由於數能夠用任意形式來表示,好比用二進制表示的數,能夠把幾個比特放在一塊兒當作一位來處理。所以咱們假設序列是二進制整數,且爲b比特長。

算法把每一個整數拆分爲b / r位數字,每一個數字都是r位長。換句話說,基於2^r禁制來表示這個數。所以,b/r使咱們算法須要運行的輪數。而2^r則是每位數字能夠取的最大值,某種意義上它是計數排序中的k。那麼,總的運行時間怎麼算呢?

咱們對這個函數關於r求導,求導數爲0時的解就能夠獲得函數的駐點,所以總能夠找到最小值。這裏將採用某種更直覺的方法,換句話說不那麼精確的方法,可是咱們仍是能夠獲得正確的結果,固然準確的說是上界。讓咱們考慮關於r的增加過程,這裏有兩個包含r的項,r越大那麼b/r也就是算法的輪數越少;可是根據後面2^r這一項可知r不可能太大,當r >> lgn時將會指數級的增加。咱們想要的是讓n而非2^r來決定整個多項式的大小,因此不妨選擇r爲這種狀況下的最大值,即r = lgn。

在實際狀況中,基數排序對於大量數據輸入運行很快,而且代碼易於維護。好比,若是咱們有32位的數,咱們把它們拆分爲好比8位的段,咱們僅僅須要作四輪線性時間的運算,只須要256位輔助空間。若是使用nglgn算法,則須要大概11輪,以下圖所示。

不幸的是,計數排序在緩存上要求更多,因此在實踐中,基數排序並不那麼快,除非你的數都很小。快速排序之類的算法會作的更好。

最後提一下,若是對任意整數排序,而且每一個數都是一個字長,目前已知的最好的排序算法,指望運行時間是n乘根號下lg(lgn),這是一個隨機算法而且很是複雜。有不少很是複雜的算法,可是它們能夠給咱們某種信息,你能夠打破對b的依賴,只要你知道b最可能是一個字長。

 

關於Introduction to Algorithms更多的學習資料將繼續更新,敬請關注本博客和新浪微博Sheridan

相關文章
相關標籤/搜索