資料結構與演算法學習筆記之程式語言中的排序函式是怎麼實現的

25minutes發表於2021-09-09

前言

   在開發過程中,人人都會用到排序,每種程式語言也會提供排序函式,可是程式語言的排序函式運用環境複雜,必須得達到最大程度的相容。我們得怎麼實現一種通用的,高效的排序函式呢?

正文

 1.最通用的排序演算法 

如下圖所示:

圖片描述

在我們選取的排序函式中,O(n2)時間複雜度適合小規模的排序,O(nlogn)時間複雜度適合大規模的排序,為了兼顧任意規模,我們選取時間複雜為O(nlogn)的演算法,如:歸併,快排,堆排序。

  歸併排序,時間複雜度符合要求,可是他不屬於原地排序演算法,空間複雜度太大,排序1G的資料就需要佔用2G的空間

 

  2.怎麼最佳化快速排序?

  快速排序的時間複雜度為O(nlogn),但是在最壞的情況時,我們每次選取的分割槽結點都選擇最後一個資料時,時間複雜都會變為0(n2),那麼最好的分割槽點就是:被分割槽點分開的兩個分割槽中,資料數量差不多。

剛好有兩種簡單常用的分割槽演算法來最佳化:

1.三數取中法

  我們從區間的首尾中,分別取出一個資料,然後對比大小,取中間值作為分割槽點。

 但是這裡有個弊端:當資料規模太大時,三數取中顯然就不夠了,需要五數取中,甚至十數取中法。

2.隨機發

  每次從區間中,選取一個元素作為分割槽點,這種方法並不能保證每次分割槽點都非常好,但是從機率學的角度來看,不太可能會出現每次分割槽點都選的很差的情況,所以這樣分割槽是比較好的。

還有很多的分割槽演算法這裡就不重點敘述了。有興趣的,自己學習一下

 

  3.Java的Sort函式

  在各大程式語言中都提供了排序函式,在Java中的sort你閱讀原始碼會發現,  

在JDK1.7版本中Arrays.sort()方法是根據傳出引數的長度的大小來判斷用哪種排序方法,一種是針對基本型別的資料,主要是有歸併排序、快速排序、插入排序、計數排序,而另一種則是針對物件型別的排序,改進的歸併排序--合併排序

   合併排序是穩定的並且合併排序比較的次數比快排,合併排序的時間複雜度是n*logn, 快速排序的平均時間複雜度也是n*logn,但是合併排序的需要額外的n個引用的空間。排序100M的資料,需要額外100M的空間。

 

函式中關於閾值的原始碼如下:

 /**
     * The maximum number of runs in merge sort.
    合併排序的最大執行次數。     */
    private static final int MAX_RUN_COUNT = 67;    /**
     * The maximum length of run in merge sort.
     * 歸併排序中的最大值,歸併排序是穩定的,時間復 雜度為O(nlogn)     */
    private static final int MAX_RUN_LENGTH = 33;    /**
     * If the length of an array to be sorted is less than this
     * constant, Quicksort is used in preference to merge sort.
     * 快速排序中的最大值,快速排序是不穩定的,時間複雜度為O(nlogn),最壞情況下是O(n^2)     */
    private static final int QUICKSORT_THRESHOLD = 286;    /**
     * If the length of an array to be sorted is less than this
     * constant, insertion sort is used in preference to Quicksort.
        如果要排序的陣列的長度小於此值
          常量,插入排序優先於Quicksort使用。     */
    private static final int INSERTION_SORT_THRESHOLD = 47;    /**
     * If the length of a byte array to be sorted is greater than this
     * constant, counting sort is used in       preference to insertion sort.
        如果要排序的位元組陣列的長度大於此值
       常量,計數排序優先於插入排序。     */
    private static final int COUNTING_SORT_THRESHOLD_FOR_BYTE = 29;    /**
     * If the length of a short or char array to be sorted is greater
     * than this constant, counting sort is used in preference to Quicksort.
        如果要排序的short或char陣列的長度更大
        比這個常量,計數排序優先於Quicksort使用。     */
    private static final int COUNTING_SORT_THRESHOLD_FOR_SHORT_OR_CHAR = 3200;

當排序的資料規模較少時:插入排序優於快速排序

在快排時,其中有一段是:當陣列大小 7<size<=40時,取首、中、末三個元素中間大小的元素作為劃分元。採用的最佳化方案也是三數取中法。

具體的大家可以閱讀原始碼;

 

4、c語言中qsort()函式

它是一種基於快速排序,歸併排序,插入排序的排序函式

當排序的資料規模很小時,如1k、2k這種我們都是用的歸併排序,雖然歸併排序需要額外的空間,但是這些小規模的資料用遞迴是速度最快的,而且空間消耗我們也負擔得起,這裡就很好的使用空間交換時間的技巧。

當排序的資料規模很大時,原始碼中採用的就是最佳化過的快排,而且最佳化的方法就是“三數取中法”。在使用快排中,當排序的區間中,元素的個數小於等於4時,qsort()就會退化為插入排序,不再使用遞迴來做快速排序,小規模資料面前O(n2)時間複雜度並不一定比O(nlogn)執行時間長。

下圖為y=n2的函式

圖片描述

 

 下圖網上尋找到的y=nlogn與y=n2在一張圖裡的圖片。你會發現O(n2) 比 O(nlogn) 增漲趨勢更猛烈,所以對於小資料量的排序,我們使用比較簡單,不需要遞迴的插入排序

圖片描述

 

原文出處:https://www.cnblogs.com/clwydjgs/p/9949854.html  

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4328/viewspace-2817393/,如需轉載,請註明出處,否則將追究法律責任。

相關文章