<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Timsort 是一個混合、穩定的排序演演算法,簡單來說就是歸併排序和二分插入排序演演算法的混合體,號稱世界上最好的排序演演算法。Timsort一直是 Python 的標準排序演演算法。Java SE 7 後新增了Timsort API ,我們從Arrays.sort
可以看出它已經是非原始型別陣列的預設排序演演算法了。所以不管是進階程式設計學習還是面試,理解 Timsort 是比較重要。
// List sort() default void sort(Comparator<? super E> c) { Object[] a = this.toArray(); //陣列排序 Arrays.sort(a, (Comparator) c); ... } // Arrays.sort public static <T> void sort(T[] a, Comparator<? super T> c) { if (c == null) { sort(a); } else { // 廢棄版本 if (LegacyMergeSort.userRequested) legacyMergeSort(a, c); else TimSort.sort(a, 0, a.length, c, null, 0, 0); } } public static void sort(Object[] a) { if (LegacyMergeSort.userRequested) legacyMergeSort(a); else ComparableTimSort.sort(a, 0, a.length, null, 0, 0); }
理解 Timsort 需要先回顧下面的知識。
指數搜尋,也被稱為加倍搜尋,是一種用於在大型陣列中搜尋元素而建立的演演算法。它是一個兩步走的過程。首先,該演演算法試圖找到目標元素存在的範圍 (L,R)
,然後在這個範圍內使用二叉搜尋來尋找目標的準確位置。時間複雜度為O(lgn)。該搜尋演演算法在大量有序陣列中比較有效。
插入排序演演算法很簡單,大體過程是從第二個元素開始,依次向前移動交換元素直到找到合適的位置。
插入排序最優時間複雜度也要O(n) ,我們可以使用二分查詢來減少插入時元素的比較次數,將時間複雜度降為logn。但是注意,二分查詢插入排序仍然需要移動相同數量的元素,但是複製陣列的時間消耗低於逐一互換操作。
特點:二分插入排序主要優點是在小資料集場景下排序效率很高。
public static int[] sort(int[] arr) throws Exception { // 開始遍歷第一個元素後的所有元素 for (int i = 1; i < arr.length; i++) { // 需要插入的元素 int tmp = arr[i]; // 從已排序最後一個元素開始,如果當前元素比插入元素大,就往後移動 int j = i; while (j > 0 && tmp < arr[j - 1]) { arr[j] = arr[j - 1]; j--; } // 將元素插入 if (j != i) { arr[j] = tmp; } } return arr; } public static int[] binarySort(int[] arr) throws Exception { for (int i = 1; i < arr.length; i++) { // 需要插入的元素 int tmp = arr[i]; // 通過二分查詢直接找到插入位置 int j = Math.abs(Arrays.binarySearch(arr, 0, i, tmp) + 1); // 找到插入位置後,通過陣列複製向後移動,騰出元素位置 System.arraycopy(arr, j, arr, j + 1, i - j); // 將元素插入 arr[j] = tmp; } return arr; }
歸併排序是利用分治策略的演演算法,包含兩個主要的操作:分割與合併。大體過程是,通過遞迴將陣列不斷分成兩半,一直到無法再分割(也就是陣列為空或只剩一個元素),然後進行合併排序。簡單來說合並操作就是不斷取兩個較小的排序陣列然後將它們組合成一個更大的陣列。
特點:歸併排序主要為巨量資料集場景設計的排序演演算法。
public static void mergeSortRecursive(int[] arr, int[] result, int start, int end) { // 跳出遞迴 if (start >= end) { return; } // 待分割的陣列長度 int len = end - start; int mid = (len >> 1) + start; int left = start; // 左子陣列開始索引 int right = mid + 1; // 右子陣列開始索引 // 遞迴切割左子陣列,直到只有一個元素 mergeSortRecursive(arr, result, left, mid); // 遞迴切割右子陣列,直到只有一個元素 mergeSortRecursive(arr, result, right, end); int k = start; while (left <= mid && right <= end) { result[k++] = arr[left] < arr[right] ? arr[left++] : arr[right++]; } while (left <= mid) { result[k++] = arr[left++]; } while (right <= end) { result[k++] = arr[right++]; } for (k = start; k <= end; k++) { arr[k] = result[k]; } } public static int[] merge_sort(int[] arr) { int len = arr.length; int[] result = new int[len]; mergeSortRecursive(arr, result, 0, len - 1); return arr; }
演演算法大體過程,如果陣列長度小於指定閥值(MIN_MERGE)直接使用二分插入演演算法完成排序,否則執行下面步驟:
升序執行就是從陣列查詢一段連續遞增(升序)或遞減(降序)子序列的過程,如果子序列為降序則將它反轉為升序,也可以將這個過程簡稱為 run
。比如陣列 [2,3,6,4,9,30],可以查詢到三個子序列,[2,3,6]、[4,9]、[30],或說3個 run
。
MIN_MERGE
這是個常數值,可以簡單理解為執行歸併的最小閥值,如果整個陣列長度小於它,就沒必要執行那麼複雜的排序,直接二分插入就行了。在 Tim Peter 的 C 實現中為 64,但實際經驗中設定為 32 效果更好,所以 java 裡面此值為 32。
降序反轉時為保證穩定性,相同元素不會被顛倒。
minrun
在合併序列的時候,如果 run
數量等於或者略小於 2 的冪次方的時候,合併效率最高;如果略大於 2 的冪次方,效率就會顯著降低。所以為了提高合併效率,需要儘量控制每個 run
的長度,通過定義一個 minrun 來表示每個 run
的最小長度,如果長度太短,就用二分插入排序把 run
後面的元素插入到前面的 run
裡面。
一般在執行排序演演算法之前,會先計算出這個 minrun(它是根據資料的特點來進行自我調整),minrun 會從32到64選擇一個數位,因此資料的大小除以 minrun 等於或略小於 2 的冪次方。比如長度是 65 ,那麼 minrun 的值就是 33;如果長度是 165,minrun 就是 42。
看下 Java 裡面的實現,如果資料長度(n) < MIN_MERGE,則返回資料長度。如果資料長度恰好是 2 的冪次方,則返回MIN_MERGE/2
也就是16,否則返回一個MIN_MERGE/2 <= k <= MIN_MERGE範圍的值k,這樣可以使的 n/k 接近但嚴格小於 2 的冪次方。
private static int minRunLength(int n) { assert n >= 0; int r = 0; // 如果低位任何一位是1,就會變成1 while (n >= MIN_MERGE) { r |= (n & 1); n >>= 1; } return n + r; }
MIN_GALLOP
MIN_GALLOP 是為了優化合並的過程設定的一個閾值,控制進入 GALLOP
模式中, GALLOP
模式放在後面講。
下面是 Timsort 執行流程圖
當棧裡面的 run
滿足合併條件時,它就將棧裡面相鄰的兩個run 進行合併。
Timsort 為了執行平衡合併(讓合併的 run 大小盡可能相同),制定了一個合併規則,對於在棧頂的三個run,分別用X、Y 和 Z 表示他們的長度,其中 X 在棧頂,它們必須始終維持一下的兩個規則:
一旦有其中的一個條件不被滿足,則將 Y 與 X 或 Z 中的較小者合併生成新的 run
,並再次檢查棧頂是否仍然滿足條件。如果不滿足則會繼續進行合併,直至棧頂的三個元素都滿足這兩個條件,如果只剩兩個run
,則滿足 Y > X 即可。
如下下圖例子
我們看下 Java 裡面的合併實現
private void mergeCollapse() { // 當存在兩個以上run執行合併檢查 while (stackSize > 1) { // 表示 Y int n = stackSize - 2; // Z <= Y + X if (n > 0 && runLen[n-1] <= runLen[n] + runLen[n+1]) { // 如果 Z < X 合併Z+Y ,否則合併X+Y if (runLen[n - 1] < runLen[n + 1]) n--; // 合併相鄰的兩個run,也就是runLen[n] 和 runLen[n+1] mergeAt(n); } else if (runLen[n] <= runLen[n + 1]) { // Y <= X 合併 Y+X mergeAt(n); } else { // 滿足兩個條件,跳出迴圈 break; } } }
原始歸併排序空間複雜度是 O(n)也就是資料大小。為了實現中間項,Timsort 進行了一次歸併排序,時間開銷和空間開銷都比 O(n)小。
優化是為了儘可能減少資料移動,佔用更少的臨時記憶體,先找出需要移動的元素,然後將較小序列複製到臨時記憶體,在按最終順序排序並填充到組合序列中。
比如我們需要合併 X [1, 2, 3, 6, 10] 和 Y [4, 5, 7, 9, 12, 14, 17],X 中最大元素是10,我們可以通過二分查詢確定,它需要插入到 Y 的第 5個位置才能保證順序,而 Y 中最小元素是4,它需要插入到 X 中的第4個位置才能保證順序,那麼就知道了[1, 2, 3] 和 [12, 14, 17] 不需要移動,我們只需要移動 [6, 10] 和 [4, 5, 7, 9],然後只需要分配一個大小為 2 臨時儲存就可以了。
在歸併排序演演算法中合併兩個陣列需要一一比較每個元素,為了優化合並的過程,設定了一個閾值 MIN_GALLOP,當B中元素向A合併時,如果A中連續 MIN_GALLOP 個元素比B中某一個元素要小,那麼就進入GALLOP模式。
根據基準測試,比如當A中連續7個以上元素比B中某一元素小時切入該模式效果才好,所以初始值為7。
當進入GALLOP模式後,搜尋演演算法變為指數搜尋,分為兩個步驟,比如想確定 A 中元素x在 B 中確定的位置
只有當一次執行的初始元素不是另一次執行的前七個元素之一時,馳騁才是有益的。這意味著初始閾值為 7。
以上就是Java實現世界上最快的排序演演算法Timsort的範例程式碼的詳細內容,更多關於Java 排序演演算法Timsort的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45