簡易版的TimSort排序演算法,timsort排序演算法

來源:互聯網
上載者:User

簡易版的TimSort排序演算法,timsort排序演算法

歡迎探討,如有錯誤敬請指正

如需轉載,請註明出處http://www.cnblogs.com/nullzx/

1. 簡易版本TimSort排序演算法原理與實現

TimSort排序演算法是Python和Java針對對象數組的預設排序演算法。TimSort排序演算法的本質是歸併排序演算法,只是在歸併排序演算法上進行了大量的最佳化。對於日常生活中我們需要排序的資料通常不是完全隨機的,而是部分有序的,或者部分逆序的,所以TimSort充分利用已有序的部分進行歸併排序。現在我們提供一個簡易版本TimSort排序演算法,它主要做了以下最佳化:

1.1 利用原本已有序的片段

首先規定一個最小歸併長度檢查數組中原本有序的片段,如果已有序的長度小於規定的最小歸併長度,則通過插入排序對已有序的片段進行進行擴充(這樣做的原因避免歸併長度較小的片段,因為這樣的效率比較低)。將有序片段的起始索引位置和已有序的長度入棧。

1.2 避免一個較長的有序片段和一個較小的有序片段進行歸併,因為這樣的效率比較低:

(1)如果棧中存在已有序的至少三個序列,我們用X,Y,Z依次表示從棧頂向下的三個已有序列片段,當三者的長度滿足X+Y>=Z時進行歸併。

   (1.1)如果X是三者中長度最大的,先將X,Y,Z出棧,應該先歸併Y和Z,然後將Y和Z歸併的結果入棧,最後X入棧

   (1.2)否則將X和Y出棧,歸併後結果入棧。注意,實際上我們不會真正的出棧,寫代碼中有一些技巧可以達到相同的效果,而且效率更高。

(2)如果不滿足X+Y>=Z的條件或者棧中僅存在兩個序列,我們用X,Y依次表示從棧頂向下的兩個已有序列的長度,如果X>=Y則進行歸併,然後將歸併後的有序片段結果入棧。

1.3 在歸併兩個已有序的片段時,採用了所謂的飛奔(gallop )模式,這樣可以減少參與歸併的資料長度

假設需要歸併的兩個已有序片段分別為X和Y,如果X片段的前m個元素都比Y片段的首元素小,那麼這m個元素實際上是不需要參與歸併的,因為歸併後這m個元素仍然位於原來的位置。同理如果Y片段的最後n個元素都比X的最後一個元素大,那麼Y的最後n個元素也不必參與歸併。這樣就減少了歸併數組的長度(簡易版沒有這麼做),也較少了待排序數組與輔助數組之間資料來回複製的長度,進而提高了歸併的效率。

2. Java原始碼
package datastruct;import java.lang.reflect.Array;import java.util.Arrays;import java.util.Random;import java.util.Scanner;public class SimpleTimSort<T extends Comparable<? super T>>{//最小歸併長度private static final int MIN_MERGE = 16;//待排序數組private final T[] a;//輔助數組private T[] aux;//用兩個數組表示棧private int[] runsBase = new int[40];private int[] runsLen = new int[40];//表示棧頂指標private int stackTop = 0;@SuppressWarnings("unchecked")public SimpleTimSort(T[] a){this.a = a;aux = (T[]) Array.newInstance(a[0].getClass(), a.length);}//T[from, to]已有序,T[to]以後的n元素插入到有序的序列中private void insertSort(T[] a, int from, int to, int n){int i = to + 1;while(n > 0){T tmp = a[i];int j;for(j = i-1; j >= from && tmp.compareTo(a[j]) < 0; j--){a[j+1] = a[j];}a[++j] = tmp;i++;n--;}}//返回從a[from]開始,的最長有序片段的個數private int maxAscendingLen(T[] a, int from){int n = 1;int i = from;if(i >= a.length){//超出範圍return 0;}if(i == a.length-1){//只有一個元素return 1;}//至少兩個元素if(a[i].compareTo(a[i+1]) < 0){//升序片段while(i+1 <= a.length-1 && a[i].compareTo(a[i+1]) <= 0){i++;n++;}return n;}else{//降序片段,這裡是嚴格的降序,不能有>=的情況,否則不能保證穩定性while(i+1 <= a.length-1 && a[i].compareTo(a[i+1]) > 0){i++;n++;}//對降序片段逆序int j = from;while(j < i){T tmp = a[i];a[i] = a[j];a[j] = tmp;j++;i--;}return n;}}//對有序片段的起始索引位置和長度入棧private void pushRun(int base, int len){runsBase[stackTop] = base;runsLen[stackTop] = len;stackTop++;}//返回-1表示不需要歸併棧中的有序片段public int needMerge(){if(stackTop > 1){//至少兩個run序列int x = stackTop - 2;//x > 0 表示至少三個run序列if(x > 0 && runsLen[x-1] <= runsLen[x] + runsLen[x+1]){if(runsLen[x-1] < runsLen[x+1]){//說明 runsLen[x+1]是runsLen[x]和runsLen[x-1]中最大的值//應該先合并runsLen[x]和runsLen[x-1]這兩段runreturn --x;}else{return x;}}elseif(runsLen[x] <= runsLen[x+1]){return x;}else{return -1;}}return -1;}//返回後一個片段的首元素在前一個片段應該位於的位置private int gallopLeft(T[] a, int base, int len, T key){int i = base;while(i <= base + len - 1){if(key.compareTo(a[i]) >= 0){i++;}else{break;}}return i;}//返回前一個片段的末元素在後一個片段應該位於的位置private int gallopRight(T[] a, int base, int len, T key){int i = base + len -1;while(i >= base){if(key.compareTo(a[i]) <= 0){i--;}else{break;}}return i;}public void mergeAt(int x){int base1 = runsBase[x];int len1 = runsLen[x];int base2 = runsBase[x+1];int len2 = runsLen[x+1];//合并run[x]和run[x+1],合并後base不用變,長度需要發生變化runsLen[x] = len1 + len2; if(stackTop == x + 3){//棧頂元素下移,省去了合并後的先出棧,再入棧runsBase[x+1] = runsBase[x+2];runsLen[x+1] = runsLen[x+2];}stackTop--;//飛奔模式,減小歸併的長度int from = gallopLeft(a, base1, len1, a[base2]);if(from == base1+len1){return;}int to = gallopRight(a, base2, len2, a[base1+len1-1]);//對兩個需要歸併的片段長度進行歸併System.arraycopy(a, from, aux, from, to - from + 1);int i = from;int iend = base1 + len1 - 1;int j = base2;int jend = to;int k = from;int kend = to;while(k <= kend){if(i > iend){a[k] = aux[j++];}elseif(j > jend){a[k] = aux[i++];}elseif(aux[i].compareTo(aux[j]) <= 0){//等號保證排序的穩定性a[k] = aux[i++];}else{a[k] = aux[j++];}k++;}}//強制歸併已入棧的序列private void forceMerge(){while(stackTop > 1){mergeAt(stackTop-2);}}//timSort的主方法public void timSort(){//n表示剩餘長度int n = a.length; if(n < 2){return;}//待排序的長度小於MIN_MERGE,直接採用插入排序完成if(n < MIN_MERGE){insertSort(a, 0, 0, a.length-1);return;}int base = 0;while(n > 0){int len = maxAscendingLen(a, base);if(len < MIN_MERGE){int abscent = n > MIN_MERGE ?  MIN_MERGE - len : n - len;insertSort(a, base, base + len-1, abscent);len = len + abscent;}pushRun(base, len);n = n - len;base = base + len;int x;while((x  = needMerge()) >= 0 ){mergeAt(x);}}forceMerge();}public static void main(String[] args){//隨機產生測試案例Random rnd = new Random(System.currentTimeMillis());boolean flag = true;while(flag){//首先產生一個全部有序的數組Integer[] arr1 = new Integer[1000];for(int i = 0; i < arr1.length; i++){arr1[i] = i;}//有序的基礎上隨機交換一些值for(int i = 0; i < (int)(0.1*arr1.length); i++){int x,y,tmp;x = rnd.nextInt(arr1.length);y = rnd.nextInt(arr1.length);tmp = arr1[x];arr1[x] = arr1[y];arr1[y] = tmp;}//逆序部分資料for(int i = 0; i <(int)(0.05*arr1.length); i++){int x = rnd.nextInt(arr1.length);int y = rnd.nextInt((int)(arr1.length*0.01)+x);if(y >= arr1.length){continue;}while(x < y){int tmp;tmp = arr1[x];arr1[x] = arr1[y];arr1[y] = tmp;x++;y--;}}Integer[] arr2 = arr1.clone();Integer[] arr3 = arr1.clone();Arrays.sort(arr2);SimpleTimSort<Integer> sts = new SimpleTimSort<Integer>(arr1);sts.timSort();//比較SimpleTimSort排序和庫函數提供的排序結果比較是否一致//如果沒有列印任何結果,說明排序結果正確if(!Arrays.deepEquals(arr1, arr2)){for(int i = 0; i < arr1.length; i++){if(!arr1[i].equals(arr2[i])){System.out.printf("%d: arr1 %d  arr2 %d\n",i,arr1[i],arr2[i]);}}System.out.println(Arrays.deepToString(arr3));flag = false;}}}}
3.TimSort演算法應當注意的問題

TimSort演算法只會對連續的兩個片段進行歸併,這樣才能保證演算法的穩定性。

最小歸併長度和棧的長度存在一定的關係,如果增大最小歸併長度,則棧的長度也應該增大,否則可能引起棧越界的風險(代碼中棧是通過長度為40的數組來實現的)。

4.完整版的TimSort演算法

實際上,完整版的TimSort演算法會在上述簡易TimSort演算法上還有大量的最佳化。比如有序序列小於最小歸併長度時,我們可以利用類似二分尋找的方式來找到應該插入的位置來對數組進行長度擴充。再比如飛奔模式中採用二分尋找的方式尋找第二個序列的首元素在第一個序列的位置,同時還可以使用較小的輔助空間完成歸併,有興趣的同學可以查看Java中的原始碼來學習。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.