1. 程式人生 > >快速排序(二) jdk原始碼中如何優化快速排序

快速排序(二) jdk原始碼中如何優化快速排序

     快速排序是一種相當棒的排序方案,相關理論內容可以參見

     在jdk的[java.util.Arrays]類中,有一個sort的函式,它實現對很多資料結構進行的排序方法,其中sort(int[] a)中主要使用的是優化後的快速排序法,本文正是基於此來講解如何優化快速排序演算法。

java原始碼:

 

★ 優化1:在小規模(size<7)陣列中,直接插入排序的效率要比快速排序高。

      沒有一種排序在任何情況下都是最優的《基於比較的內部排序總結 》。 O(N^2)級別的排序看起來似乎比所有先進排序要差的多。但實際上也並非如此,Arrays中的sort()演算法就給了我們一個很好的例子。當待排陣列規模非常小的時候(JDK中規模的閾值為INSERTIONSORT_THRESHOLD=7),直接插入排序反而要比快排,歸併排序要好。

           這個道理很簡單。陣列規模小,簡單演算法的比較次數不會比先進演算法多多少。相反,諸如快排,歸併排序等先進演算法使用遞迴操作,所付出的執行代價更高。

★ 優化2:精心選擇劃分元素,即樞軸。

      快排有一種最差的情況,即蛻化成效率最差的起跑排序(見《 交換排序 》)。 導致這種情況產生的主要原因就是樞軸的選擇並不能把整個陣列劃分成兩個大致相等的部分。比如對於基本有序的陣列,選擇第一個元素作為樞軸就會產生這種蛻化。

      既然如此,我們可以看看Arryas.sort()是如何為我們選擇樞軸的。

      ● 如果是小規模陣列(size<=7),直接取中間元素作為樞軸。 

      ● 如果是中等規模陣列(7=<size<=40),則在陣列首、中、尾三個位置上的數中取中間大小的數作為樞軸
      ● 如果是大規模陣列(size>40),則在9個指定的數中取一個偽中數(中間大小的數s)

      中小規模時,這種取法儘量可以避免陣列的較小數或者較大數成為樞軸。值得一提的是大規模的時候,首先在陣列中尋找9個數據(可以通過原始碼發現這9個數據的位置較為平均的分佈在整個陣列上);然後每3個數據找中位數;最後在3箇中位數上再找出一箇中位數作為樞軸。

      仔細想想,這種精心選擇的樞軸,使得快排的最差情況成為了極小概率事件了。

★ 優化3:根據樞軸v劃分,形成一個形如  (<v)*   v* (>v)* 的陣列

      普通快排演算法,都是使得樞軸元素移動到陣列的較中間位置。樞軸之前的元素全部小於或等於樞軸,之後的元素全部大於樞軸。但與樞軸相等的元素並不能移動到樞軸附近位置。這一點在Arrays.sort()演算法中有很大的優化。

      我們舉個例子來說明Arrays的優化細節       15、93、15、41、6、15、22、7、15、20

      第一次樞軸:v=15

      階段一,形成 v* (<v)* (>v)* v* 的陣列:

                                          15、15、 7、6、 41、20、22、93、 15、15

                  我們發現,與樞軸相等的元素都移動到了陣列的兩邊。而比樞軸小的元素和比樞軸大的元素也都區分開來了。

      階段二,將樞軸和與樞軸相等的元素交換到陣列中間的位置上

                                          7、6、 15、15、 15、15、 41、20、22、93

      階段三,遞迴排序與樞軸不相等都元素區間{7、6}和{41、20、22、93}

      仔細想想,對於重複元素較多的陣列,這種優化無疑能到達更好的效率。