1. 程式人生 > >排序演算法總結之歸併排序

排序演算法總結之歸併排序

一,歸併排序介紹

歸併排序是一個典型的基於分治的遞迴演算法。它不斷地將原陣列分成大小相等的兩個子陣列(可能相差1),最終當劃分的子陣列大小為1時(下面程式碼第17行left小於right不成立時) ,將劃分的有序子陣列合併成一個更大的有序陣列。為什麼是有序子陣列???

歸併排序的遞迴公式:T(N) = 2T(N/2) + O(N)

從公式中可以看出:將規模為 N 的原問題分解成兩個規模 N/2 的兩個子問題;並且,合併這兩個子問題的代價是 O(N)---[後面的 +O(N) 表示合併的代價]

二,歸併排序演算法分析

 歸併排序演算法有兩個基本的操作,一個是分,也就是把原陣列劃分成兩個子陣列的過程。另一個是治,它將兩個有序數組合併成一個更大的有序陣列。

它將陣列平均分成兩部分: center = (left + right)/2,當陣列分得足夠小時---陣列中只有一個元素時,只有一個元素的陣列自然而然地就可以視為是有序的,此時就可以進行合併操作了。因此,上面講的合併兩個有序的子陣列,是從 只有一個元素 的兩個子陣列開始合併的。

合併後的元素個數:從 1-->2-->4-->8......

比如初始陣列:[24,13,26,1,2,27,38,15]

①分成了兩個大小相等的子陣列:[24,13,26,1]    [2,27,38,15]

②再劃分成了四個大小相等的子陣列:[24,13]   [26,1]    [2,27]    [38,15]

③此時,left < right 還是成立,再分:[24]   [13]   [26]    [1]    [2]     [27]    [38]   [15]

此時,有8個小陣列,每個陣列都可以視為有序的陣列了!!!,每個陣列中的left == right,從遞迴中返回(從19行--20行的程式碼中返回),故開始執行合併(第21行):

merge([24],[13]) 得到 [13,24]

merge([26],[1]) 得到[1,26]

.....

.....

最終得到 有序陣列。

三,歸併排序演算法實現

 1 public class MergeSort {
2 3 public static <T extends Comparable<? super T>> void mergeSort(T[] arr) { 4 T[] tmpArray = (T[]) new Comparable[arr.length]; 5 mergeSort(arr, tmpArray, 0, arr.length - 1); 6 } 7 8 /** 9 * 10 * @param arr an array of Comparable items 11 * @param tmpArray an array to place the merge result 12 * @param left the left-most index of the array 13 * @param right right-most index of the array 14 */ 15 private static <T extends Comparable<? super T>> void mergeSort(T[] arr, 16 T[] tmpArray, int left, int right) { 17 if (left < right) { 18 int center = (left + right) / 2; 19 mergeSort(arr, tmpArray, left, center); 20 mergeSort(arr, tmpArray, center + 1, right); 21 merge(arr, tmpArray, left, center + 1, right); 22 } 23 } 24 25 /** 26 * 27 * @param arr an array of Comparable items 28 * @param tmpArray an array to place the merge result 29 * @param leftPos the left-most index of the subarray 30 * @param rightPos the index of the start of the second half 31 * @param rightEnd the right-most index of the subarray 32 */ 33 private static <T extends Comparable<? super T>> void merge(T[] arr, 34 T[] tmpArray, int leftPos, int rightPos, int rightEnd) { 35 int leftEnd = rightPos - 1; 36 int numElements = rightEnd - leftPos + 1; 37 int tmpPos = leftPos;// 只使用tmpArray中某一部分割槽域 38 while (leftPos <= leftEnd && rightPos <= rightEnd) { 39 if (arr[leftPos].compareTo(arr[rightPos]) <= 0) 40 tmpArray[tmpPos++] = arr[leftPos++]; 41 else 42 tmpArray[tmpPos++] = arr[rightPos++]; 43 } 44 45 while (leftPos <= leftEnd) 46 tmpArray[tmpPos++] = arr[leftPos++];// copy rest of left half 47 while (rightPos <= rightEnd) 48 tmpArray[tmpPos++] = arr[rightPos++];// copy rest of right half 49 50 // copy tmpArray back 51 for (int i = 0; i < numElements; i++, rightEnd--) 52 arr[rightEnd] = tmpArray[rightEnd];//只拷貝當前 merge 的部分陣列 53 54 /** 55 * 複製了整個陣列中的所有元素 56 for(int i = 0; i < tmpArray.length; i++) 57 arr[i] = tmpArray[i]; 58 */ 59 } 60 61 //for test purpose 62 public static void main(String[] args) { 63 Integer[] arr = {24,13,26,1,2,27,38,15}; 64 mergeSort(arr); 65 for (Integer i : arr) 66 System.out.print(i + " "); 67 } 68 }

①第3行的公共方法,是對外的排序介面,首先建立一個臨時陣列tmpArray,用來儲存合併過程中,兩個子陣列臨時合併的結果。將tmpArray作為引數傳遞給遞迴呼叫的方法,而不是在執行遞迴呼叫的方法裡面建立臨時陣列,這樣可以大大地減少臨時陣列的建立。若在遞迴呼叫的方法裡建立臨時陣列,每一層遞迴呼叫,都會建立一個臨時陣列。

②第15行的私有方法,是執行遞迴呼叫的方法。在某次具體的遞迴呼叫中,只用到了tmpArray中的某一部分空間(leftEnd 和 rightEnd之間的空間)。

③第38行while迴圈,比較兩個子陣列中的元素,誰小就把誰放到tmpArray中。

 ④第45行和第47行的兩個while迴圈完成的功能是:當合並兩個有序的子陣列時,一個子陣列中的元素已經全部放到tmpArray中去了,另一個子陣列中還剩下有元素,故將剩下的所有元素直接複製到tmpArray中。

⑤第51行for迴圈,將本次merge完成的兩個子陣列複製到原陣列中去。注意,它只複製本次參與合併的兩個子陣列中的元素。為什麼要複製到原陣列中去呢?因為在下一次的合併過程中,需要合併的是更大的子陣列,這個更大的陣列,就是由上次合併的生成的有序小陣列組成的。比如:

在合併這兩個陣列時:[24]   [13]

下一次合併的則是:[13,24]  [1,26]

四,歸併排序演算法複雜度分析

歸併排序中,用到了一個臨時陣列,故空間複雜度為O(N)

由歸併排序的遞迴公式:T(N) = 2T(N/2) + O(N) 可知時間複雜度為O(NlogN)

陣列的初始順序會影響到排序過程中的比較次數,但是總的而言,對複雜度沒有影響。平均情況 or 最壞情況下 它的複雜度都是O(NlogN)

此外,歸併排序中的比較次數是所有排序中最少的。原因是,它一開始是不斷地劃分,比較只發生在合併各個有序的子陣列時。

因此,JAVA的泛型排序類庫中實現的就是歸併排序。因為:對於JAVA而言,比較兩個物件的操作代價是很大的(根據Comparable介面的compareTo方法進行比較),而移動兩個物件,其實質移動的是引用,代價比較小。(排序本質上是兩種操作:比較操作和移動操作)

java.util.Arrays.sort(T[] arr)使用的是歸併排序

java.util.Arrays.sort(int[] arr) 使用的是快速排序

2018-11-24更新:

JDK7中使用 TimSort演算法取代了原來的歸併排序。優化點:①歸併排序中的“分”不再直至劃分到單個元素;②引入binarySort二分查詢思想。

五,參考資料

相關推薦

排序演算法總結歸併排序

一,歸併排序介紹 歸併排序是一個典型的基於分治的遞迴演算法。它不斷地將原陣列分成大小相等的兩個子陣列(可能相差1),最終當劃分的子陣列大小為1時(下面程式碼第17行left小於right不成立時) ,將劃分的有序子陣列合併成一個更大的有序陣列。為什麼是有序子陣列??? 歸併排序的遞迴公式:T(N) = 2

排序演算法大雜燴歸併排序

排序演算法大雜燴主幹文章傳送門 歸併排序 #include <iostream> #include <vector> using namespace std; //典型的分治策略,拆分,解決,合併 void Merge(vector<int>

圖解排序演算法(四)歸併排序

package sortdemo; import java.util.Arrays; /** * Created by chengxiao on 2016/12/8. */ public class MergeSort { public static void main(S

排序演算法總結排序 Bucket Sort

private void bucketsort(double[] A) { int n = A.length; ArrayList<Double>[] list = new ArrayList[n]; for(int i=0;i<n;i++) { int tem

排序演算法總結排序

一,堆排序介紹 堆是一個優先順序佇列,對於大頂堆而言,堆頂元素的權值最大。將 待排序的陣列 建堆,然後不斷地刪除堆頂元素,就實現了排序。關於堆,參考:資料結構--堆的實現之深入分析 下面的堆排序演算法將陣列中的元素從小到大排序,用大頂堆來實現。 二,堆排序演算法分析  現給定了一維陣列,需要將陣列

排序演算法總結快速排序

一,快速排序介紹 快速排序與歸併排序一樣,也是基於分治的遞迴演算法,體現在:在每一趟快速排序中,需要選出樞軸元素,然後將比樞軸元素大的陣列元素放在樞軸元素的右邊,比樞軸元素小的陣列元素都放在樞軸元素的左邊。然後,再對分別對 樞軸元素左邊 和 樞軸元素右邊的元素進行快速排序。 二,快速排序演算法分析

排序演算法總結插入排序

一,插入排序介紹  插入排序是基於比較的排序。所謂的基於比較,就是通過比較陣列中的元素,看誰大誰小,根據結果來調整元素的位置。 因此,對於這類排序,就有兩種基本的操作:①比較操作; ②交換操作 其中,對於交換操作,可以優化成移動操作,即不直接進行兩個元素的交換,還是用一個樞軸元素(tmp)將當前元素先儲

排序演算法總結氣泡排序

氣泡排序(Bubble Sort,臺灣譯為:泡沫排序或氣泡排序)是一種簡單的排序演算法。它的基本思想就是兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序的記錄為止。它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地

資料結構與演算法C++歸併排序(續)

上一篇部落格中實現的是自上以下的歸併排序,自上而下需要先不斷將陣列進行對半拆分(遞迴實現),然後再合併排序 其實也可以自下而上實現歸併排序,這樣使用for迴圈就可以實現,省掉了遞迴的操作 首先對陣列的每一個元素進行兩兩歸併(相鄰的兩個元素合併成一個有序陣列),然後將合併好的兩個元素的有序

資料結構與演算法C++歸併排序

上兩篇部落格使用的選擇排序和插入排序的演算法複雜度都是O(n2),這在陣列元素比較多的時候和一些演算法複雜度為O(nlogn)的快速排序演算法相比,差距還是很明顯的,如下圖 當有10萬個元素的時候,快速排序演算法比普通的選擇排序演算法要快了6000倍, 本篇部落格介紹的是快速排序演算法

七大內部排序演算法總結(插入排序、希爾排序、氣泡排序、簡單選擇排序、快速排序歸併排序、堆排序

寫在前面:         排序是計算機程式設計中的一種重要操作,它的功能是將一個數據元素的任意序列,重新排列成一個按關鍵字有序的序列。因此排序掌握各種排序演算法非常重要。對下面介紹的各個排序,我們假定所有排序的關鍵字都是整數、對傳入函式的引數預設是已經檢查好了的。只

【Python排序搜尋基本演算法歸併排序

        歸併排序最令人興奮的特點是:不論輸入是什麼樣的,它對N個元素的序列排序所用時間與NlogN成正比。程式碼如下: def mergesort(seq): if len(seq)<

排序演算法總結折半插入排序

基本思想折半插入排序是對直接插入排序的簡單改進,對於直接插入排序而言,當第i-1趟需要將第i個元素插入前面的0~i-1個元素序列中時,總是需要從i-1個元素開始,逐個比較每個元素,直到找到它的位置。這顯然沒有利用前面0~i-1個元素已經有序這個特點,而折半插入排序則改進了這一

【Java】 大話資料結構(17) 排序演算法(4) (歸併排序) 資料結構與演算法合集 資料結構與演算法合集

本文根據《大話資料結構》一書,實現了Java版的堆排序。 更多:資料結構與演算法合集 基本概念   歸併排序:將n個記錄的序列看出n個有序的子序列,每個子序列長度為1,然後不斷兩兩排序歸併,直到得到長度為n的有序序列為止。   歸併方法:每次在兩個子序列中找到較小的那一個賦值給合併序列(通過指標進行操

排序演算法大雜燴排序

排序演算法大雜燴主幹文章傳送門 堆排序 #include <iostream> #include <vector> /* 非降序排序 時間複雜度:o(nlgn) 空間複雜度:o(nlgn) 建堆:o(n) 維護堆:o(lgn) 不穩定 */ using n

排序演算法大雜燴快速排序

排序演算法大雜燴主幹文章傳送門 快速排序 接著主文章結構繼續分析快速排序 快速排序實現主流上分為兩種,第一種為快排的開山鼻祖 C

排序演算法5】歸併排序

歸併排序是分治法的一種典型應用。 歸併排序的原理是合併兩個有序序列是簡單的。 先對元素進行分割,分割到最後只有單一元素的時候進行歸併。兩兩一組自底向上歸併。 #include <stdio.h> #include <Windows.h> #include "M

排序演算法6——圖解歸併排序及其遞迴與非遞迴實現

排序演算法1——圖解氣泡排序及其實現(三種方法,基於模板及函式指標) 排序演算法2——圖解簡單選擇排序及其實現 排序演算法3——圖解直接插入排序以及折半(二分)插入排序及其實現 排序演算法4——圖解希爾排序及其實現 排序演算法5——圖解堆排序及其實現 排序演算法6——圖解歸併排序及其遞迴與非

排序演算法入門氣泡排序

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!                在開發中,對一組資料進行有序地排列是經常需要做的事情,所以掌握幾種甚至更多的排序演算法是絕對有必要的本文章介紹的是排序演算法中較簡單的一種演算法:氣泡排序題外話

八大排序演算法詳解——歸併排序

基本思想 n個記錄的檔案的直接選擇排序可經過n-1趟直接選擇排序得到有序結果: 初始狀態:無序區為R[1..n],有序區為空。 第1趟排序: 在無序區R[1..n]中選出關鍵字最小的記錄R[k],將它與無序區的第1個記錄R[1] 交換,使R[1..1