1. 程式人生 > >尾遞迴:實際中為什麼快排會比堆排快?

尾遞迴:實際中為什麼快排會比堆排快?

尾遞迴

  • 尾遞迴就是從最後開始計算, 每遞迴一次就算出相應的結果, 也就是說, 函式調用出現在呼叫者函式的尾部, 因為是尾部, 所以根本沒有必要去儲存任何區域性變數. 直接讓被呼叫的函式返回時越過呼叫者, 返回到呼叫者的呼叫者去.
  • 遞迴解題相對常用的演算法如普通迴圈等,執行效率較低。因此,應該儘量避免使用遞迴,除非沒有更好的演算法或者某種特定情況,遞迴更為適合的時候。在遞迴呼叫的過程當中系統為每一層的返回點、區域性量等開闢了棧來儲存,因此遞迴次數過多容易造成棧溢位。
  • 函式調用出現在呼叫者函式的尾部, 因為是尾部, 所以根本沒有必要去儲存任何區域性變數. 直接讓被呼叫的函式返回時越過呼叫者, 返回到呼叫者的呼叫者去。尾遞迴就是把當前的運算結果(或路徑)放在引數裡傳給下層函式,深層函式所面對的不是越來越簡單的問題,而是越來越複雜的問題,因為引數裡帶有前面若干步的運算路徑。
//quick sort code
QUICK-SORT(A[],p,r)
    if(p < r)
        q = PARTITION(A,p,r)
        QUICK-SORT(A,p,q-1)
        QUICK-SORT(A,q+1,r)

由上面可看,quick-sort在 在編譯時,能夠進行 尾遞迴 優化,減少執行棧深度,提升效率和速度。

裡面強調,其主要問題是堆排在每次 將首元素A[1] 與 A[n] 交換之後,再將新的A[1]下移至合適的位置(即保持堆的最大化性質)。問題就出現在這裡,因為每次與A[1]交換的A[n]元素一定十分的小,它在交換後能夠保持呆在A[1]的概率非常的低,必須要經過很多次的比較和向下挪動才能到達合適位置。而最優的方式是,每次對調的位置,有1/2的概率使得A[n]能夠保持呆下去。堆排雖然和快排一樣複雜度都是O(NlogN)但堆排複雜度的常係數更大。

裡面的引出問題是一個稱球的智力題,從這個問題去分析快排和堆排還有基數排序的效能分析。很有意思。
引用大牛的數學之美番外篇裡邊的一段話

另外,這幾天我重新把TAOCP 第三卷(第二版)翻出來看了看 Knuth 怎麼說這個問題的, 發現真是牛大了:

先說效能:

pp148, section 5.2.3 說:

When N = 1000, the approximate average runiing time on MIX are 160000u for heapsort 130000u for shellsort 80000u  for quicksort

這裡,  Knuth 同學發現一般情況下 heapsort 表現很不好. 於是,在下文他就說,習題18
(pp156, 難度21) (R.W.Floyd) During the selection phase of heapsort, the key K tends to be quite small, so that nearly all the comparisons in step H6 find K<K_j. Show how to modify the algorithm so that K is not compared with K_j in the main loop of the computation, thereby nearly cutting the average number of comparisons in half.