歸併排序就這麼簡單
從前面已經講解了氣泡排序、選擇排序、插入排序,快速排序了,本章主要講解的是歸併排序,希望大家看完能夠理解並手寫出歸併排序快速排序的程式碼,然後就通過面試了!如果我寫得有錯誤的地方也請大家在評論下指出。
歸併排序的介紹
來源百度百科:
歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序演算法,該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為二路歸併。
過程描述:
歸併過程為:比較a[i]和b[j]的大小,若a[i]≤b[j],則將第一個有序表中的元素a[i]複製到r[k]中,並令i和k分別加上1;否則將第二個有序表中的元素b[j]複製到r[k]中,並令j和k分別加上1,如此迴圈下去,直到其中一個有序表取完,然後再將另一個有序表中剩餘的元素複製到r中從下標k到下標t的單元。歸併排序的演算法我們通常用遞迴實現,先把待排序區間[s,t]以中點二分,接著把左邊子區間排序,再把右邊子區間排序,最後把左區間和右區間用一次歸併操作合併成有序的區間[s,t]。
原理:
歸併操作的工作原理如下:
第一步:申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合併後的序列
第二步:設定兩個指標,最初位置分別為兩個已經排序序列的起始位置
第三步:比較兩個指標所指向的元素,選擇相對小的元素放入到合併空間,並移動指標到下一位置
重複步驟3直到某一指標超出序列尾
將另一序列剩下的所有元素直接複製到合併序列尾
下面我就來做個小小的總結:
- 將兩個已排好序的陣列合併成一個有序的陣列,稱之為歸併排序
- 步驟:遍歷兩個陣列,比較它們的值。誰比較小,誰先放入大陣列中,直到陣列遍歷完成
一、演算歸併排序過程
現在我有兩個已經排好順序的陣列:int[] arr1 = {2, 7, 8}
和int[] arr2 = {1, 4, 9}
,我還有一個大陣列來裝載它們int[] arr = new int[6];
1.1
那麼,我將兩個陣列的值進行比較,誰的值比較小,誰就放入大陣列中!
首先,拿出arr1[0]
和arr2[0]
進行比較,顯然是arr2[0]
比較小,因此將arr2[0]
放入大陣列中,同時arr2
指標往後一格
所以,現在目前為止arr = {1}
1.2
隨後,拿arr1[0]
和arr2[1]
進行比較,顯然是arr1[0]
比較小,將arr1[0]
放入大陣列中,同時arr1
指標往後一格
所以,現在目前為止arr = {1,2}
1.3
隨後,拿arr1[1]
和arr2[1]
進行比較,顯然是arr2[1]
比較小,將arr2[1]
放入大陣列中,同時arr2
指標往後一格
所以,現在目前為止arr = {1,2,4}
........
遍歷到最後,我們會將兩個已排好序的陣列變成一個已排好序的陣列arr = {1,2,4,7,8,9}
二、歸併排序前提分析(分治法)
從上面的演算我們就直到,歸併排序的前提是需要兩個已經排好順序的陣列,那往往不會有兩個已經排好順序的陣列給我們的呀**(一般是雜亂無章的一個陣列)**,那這個演算法是不是很雞肋的呢??
其實並不是的,首先假設題目給出的陣列是這樣子的:int[] arr = {2, 7, 8, 1, 4, 9};
當我們要做歸併的時候就以arr[3]
也就元素為1的那個地方分開。是然後用一個指標L
指向arr[0]
,一個指標M
指向arr[3]
,用一個指標R
指向arr[5]
(陣列最後一位)。有了指標的幫助,我們就可以將這個陣列切割成是兩個有序的陣列了(操作的方式就可以和上面一樣了)
可是上面說了,一般給出的是雜亂無章的一個陣列,現在還是達不到要求。比如給出的是這樣一個陣列:int[] arrays = {9, 2, 5, 1, 3, 2, 9, 5, 2, 1, 8};
此時,我們就得用到分治的思想了:
- 那麼我們也可以這樣想將
int[] arr = {2, 7, 8, 1, 4, 9};
陣列分隔成一份一份的,arr[0]
它是一個有序的"陣列",arr[1]
它也是一個有序的"陣列",利用指標(L,M,R)又可以像操作兩個陣列一樣進行排序。最終合成{2,7}
.......再不斷拆分合並,最後又回到了我們的arr = {1,2,4,7,8,9}
,因此歸併排序是可以排序雜亂無章的陣列的
這就是我們的分治法--->將一個大問題分成很多個小問題進行解決,最後重新組合起來
三、歸併程式碼實現
實現步驟:
- 拆分
- 合併
........
public static void main(String[] args) {
int[] arrays = {9, 2, 5, 1, 3, 2, 9, 5, 2, 1, 8};
mergeSort(arrays, 0, arrays.length - 1);
System.out.println("公眾號:Java3y" + arrays);
}
/**
* 歸併排序
*
* @param arrays
* @param L 指向陣列第一個元素
* @param R 指向陣列最後一個元素
*/
public static void mergeSort(int[] arrays, int L, int R) {
//如果只有一個元素,那就不用排序了
if (L == R) {
return;
} else {
//取中間的數,進行拆分
int M = (L + R) / 2;
//左邊的數不斷進行拆分
mergeSort(arrays, L, M);
//右邊的數不斷進行拆分
mergeSort(arrays, M + 1, R);
//合併
merge(arrays, L, M + 1, R);
}
}
/**
* 合併陣列
*
* @param arrays
* @param L 指向陣列第一個元素
* @param M 指向陣列分隔的元素
* @param R 指向陣列最後的元素
*/
public static void merge(int[] arrays, int L, int M, int R) {
//左邊的陣列的大小
int[] leftArray = new int[M - L];
//右邊的陣列大小
int[] rightArray = new int[R - M + 1];
//往這兩個陣列填充資料
for (int i = L; i < M; i++) {
leftArray[i - L] = arrays[i];
}
for (int i = M; i <= R; i++) {
rightArray[i - M] = arrays[i];
}
int i = 0, j = 0;
// arrays陣列的第一個元素
int k = L;
//比較這兩個陣列的值,哪個小,就往陣列上放
while (i < leftArray.length && j < rightArray.length) {
//誰比較小,誰將元素放入大陣列中,移動指標,繼續比較下一個
if (leftArray[i] < rightArray[j]) {
arrays[k] = leftArray[i];
i++;
k++;
} else {
arrays[k] = rightArray[j];
j++;
k++;
}
}
//如果左邊的陣列還沒比較完,右邊的數都已經完了,那麼將左邊的數抄到大陣列中(剩下的都是大數字)
while (i < leftArray.length) {
arrays[k] = leftArray[i];
i++;
k++;
}
//如果右邊的陣列還沒比較完,左邊的數都已經完了,那麼將右邊的數抄到大陣列中(剩下的都是大數字)
while (j < rightArray.length) {
arrays[k] = rightArray[j];
k++;
j++;
}
}
複製程式碼
我debug了一下第一次的時候,就可以更容易理解了:
- 將大陣列的前兩個進行拆分,然後用陣列裝載起來
- 比較小陣列的元素哪個小,哪個小就先放入大陣列中
上面的兩個步驟不斷迴圈,最後得出有序的陣列:
四、歸併排序的優化
來源:www.cnblogs.com/noKing/p/79…
我這裡整理一下要點,有興趣的同學可到上面的連結上閱讀:
- 當遞迴到規模足夠小時,利用插入排序
- 歸併前判斷一下是否還有必要歸併
- 只在排序前開闢一次空間
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y