1. 程式人生 > >斯坦福演算法分析和設計_2. 排序演算法MergeSort

斯坦福演算法分析和設計_2. 排序演算法MergeSort

  Motivate MergeSort是個相對古老的演算法了,為什麼現在我們還要討論這麼古老的東西呢?有幾個原因:
  • 它雖然年齡很大了,但是在實踐中一直被沿用,仍然是很多程式庫中的標準演算法之一。
  • 實現它的本質是分治思想,是一個理解分治演算法思想的好例子,好起點。
  • 本文會使用“遞迴樹”來對它進行執行時間分析,後面會集合這種思路生成“主方法”。
題目   輸入一個數組,數組裡面的每個數字是不重複的,輸出是已經排序好的陣列。 比如輸入的是: 期望輸出的是: 可能之前我們有所知道一些排序演算法,比如SelectionSort,掃描全陣列找到最小元素,把它放到輸出陣列的第一位,接著掃描複製次小的元素,以此類推;比如BubbleSort,對相鄰無序的元素進行比較,執行反覆的交換,直到最後陣列完成排序。等等。但這些演算法的問題就是執行時間是平方級的。那我們來看看今天這個排序演算法用更少的執行時間是怎麼實現的。 例子 想要理解MergeSort演算法是如何執行的,一個最簡單的方法就是看看具體的例子。
整體過程就是: 它把陣列 [5, ,4, 1, 8, 7, 2, 6, 3] 劃分為更小的陣列(子問題)[5, 4, 1, 8] 和 [7, 2, 6, 3]排序,通過神奇的遞迴操作,得到每個排序後的子陣列,再將子數組合並起來。 虛擬碼 將上面的圖換成虛擬碼就是這樣的過程 而這個Merge程式怎麼實現呢? 由上面的圖我們可以知道,Merge的時候,其實輸入兩個已經排序好的陣列C, D,再把它們排序輸出到B。 索引 k 操控的是輸出陣列,索引 i,j 操控的是已排序好的C和D子陣列,都是從左向右掃描。每一次的for迴圈,其實就是找C和D中最小的數字,因為C和D是已排序好的陣列,所以最小的數字就是i或j對應的元素。比較後把它放入輸出陣列B中,並將對應的索引+1,這樣下次迴圈就跳過已複製的元素了。所以最後的陣列B輸出是按序方式生成的。 演算法分析 我們先來對Merge程式算算它的執行運算元量。 第1,2行有一次賦值操作。 第3行是一個for迴圈,每一個for迴圈裡,有比較操作(C[i]和D(i)比較),賦值操作(B[k]的賦值),遞增操作(i或j加1),迴圈變數k還要加1,所以每一次迴圈一共有4次操作。 一共就是4n+2次操作,為了後面的計算方便,當n>=1時,4n+2<6n(去掉常數項), 我們取6n次操作作為Merge程式操作上界。 我們現在再來看MergeSort的執行時間。 為了簡單起見,假如輸入陣列的長度是n的2次方(如果沒有這個假設只需要額外工作就能消除這個假設),我們用遞迴樹的方法來分析執行時間的上界,每一個節點就表示一次遞迴呼叫。
最外層是整個原始的輸入陣列,它在進行MergeSort的時候會有2個遞迴呼叫,所以這是一個二叉樹(每個節點都有兩個子節點),第一層的2個節點就是原始陣列的左半部分和右半部分,再次遞迴最後到達最底層,一個長度為1或0的陣列。我們需要知道幾個數量: 原始陣列長度是n,遞迴樹有多少層? 由於每深入一層,陣列長度就縮小一半,第0層是n,第1層是n/2(除了一次2),第2層是n/4(除了2次2),最後一層的陣列長度是不大於1的,就是除以了log2(n)次2,所以最後一層是log2(n)層。(也可以假定n/2^a = 1, 求解a)如果沒有n是2的次方這個假設,就向上取整。一共就是log2(n) +1層。
遞迴樹的第j層有多少個節點(子問題)?每個節點的陣列長度是多少? 因為每一層的遞迴數量是前一層的兩倍,所以第j層就有2^j個子問題。每個節點的長度,總長度是n,均分到每個節點就是n/(2^j)個長度。 所以總的執行時間就是: 層數 * 每一層的工作量 = 層數 * (第 j 層的子問題數量 * 每個第j層子問題完成的工作數) = 層數 * (第 j 層的子問題數量 * (每個第j層子問題的規模 * Merge的時間)) = (log2(n)+1) * ( 2^j * n/(2^j) * 6n) =6n * log2(n) +6n = O(nlogx(n)) 這時候,我們就可以理直氣壯的說遞迴的分治演算法比其它更簡單的演算法要快的多啦。看圖直觀感受一下 當資料非常大的時候,它是非常有優勢的,指數函式增長十分的緩慢。 今日互動 有什麼不明白的歡迎在評論區留言。