- 排序(一)直接插入
- 排序(二)希爾排序
- 排序(三)冒泡排序
- 排序(四)快速排序
- 排序(五)直接選擇排序
- 排序(六)歸併排序
本節主要簡單介紹一下歸併排序算法,歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲二路歸併。摘自百度百科。
二、分析
在「數據結構與算法分析」中是這麼描述歸併排序算法的:如果N=1,那麼只有一個元素需要排序,答案是顯然的。否則,遞歸地將前半部分數據和後半部分數據各自歸併排序,得到排序後的兩部分數據,然後使用我們的合併算法將這兩部分合併到一起。
不過我們需要注意的是,並不是將序列遞歸拆分直到每部分數據全部N=1,才能開始進行合併的哦。下面我們就分別來介紹下遞歸拆分和合並的方法:
(1)拆分
拆分的方法很簡單,我們以數組array{12, 9, 13, 17, 10, 7, 13, 18}爲例,這是我們的拆分圖示,根據圖片我們知道第1次拆分,我們將序列拆分爲array1{12, 9, 13, 17}和序列array2{10, 7, 13, 18};第二次拆分,我們將序列分爲array1{12, 9}、array2{13, 17}、array3{10, 7}和array4{13, 18};第三次拆分,我們將序列分爲array1{12}、array2{9}、array3{13}、array4{17}、array5{10}、array6{7}、array7{13}和array8{18},到此,我們的拆分結束
(2)合併方法
合併方法就是:將兩個已排序的表,輸出到第三個表中。在這裏我們就以序列array1{9, 12, 13, 17}和array2{7, 10, 13, 18}爲例,首先我們需要創建一個臨時輸出數組array3,其中array3.length = array1.length+array2.length,隨後我們定義三個計數器Apos,Bpos,Cpos,分別表示其在各數組內的下標
首先我們比較array1[Apos] 與array2[Bpos]的大小,隨後將小的數拷貝到array3中,並將相應數組下標往後挪1,本例中即比較數字9 與數字7的大小,隨後將7 拷貝到array3中,並加Bpos+1、Cpos+1
同理,我們使用相同的方法進行下一次的操作
同理,我們使用相同的方法進行下一次的操作
同理,我們使用相同的方法進行下一次的操作
同理,我們使用相同的方法進行下一次的操作,在這裏我們需要注意的是當前半部分和後半部分元素相等,爲了穩定性,我們需要將前半部分的數字拷貝到臨時數組裏
同理,我們使用相同的方法進行下一次的操作
同理,我們使用相同的方法進行下一次的操作後,此時Apos == array1.length,所以比較結束,我們只需要將array2中剩餘的元素全部拷貝到臨時輸出數組中即可,在本例中選取的數字比較特殊,當array1拷貝完畢後,array2只剩一個元素
最後我們將array2中剩餘的元素拷貝到臨時輸出數組中;最後我們還需要將臨時數組中的數據覆蓋到原數據上
在這裏我們是以序列array1{9, 12, 13, 17}和array2{7, 10, 13, 18}爲例,以(1)中拆分後的數組爲例同樣如此:
我們使用同樣的方法將array1{12}和array2{9}合併成爲array1{9, 12};我們將array3{13}和array4{17}合併成array2{13, 17};我們將array5{10}和array6{7}合併成array3{7, 10};我們將array7{13}和array8{18}合併成array4{13, 18}
之後我們將array1{9, 12}和array2{13, 17}合併成array1{9, 12, 13, 17};將array3{7, 10}和array4{13, 18}合併成array2{7, 10, 13, 18}
最後我們將數組array1{9, 12, 13, 17}和array2{7, 10, 13, 18}合併成數組array{7, 9, 10, 12, 13, 13, 17, 18}
三、代碼展示
這是我們歸併排序的代碼
/**
* 歸併排序
* @param array 原始數組,即待排序數組
*/
private static void mergeSort(int[] array) {
int length = array.length;
// 創建一個臨時數組
int[] tempArray = new int[length];
mergeSort(array, tempArray, 0, length-1);
}
通過遞歸方法,將不斷分解出來的前後兩部分各自進行合併運算
/**
* 通過遞歸方法,將不斷分解出來的前後兩部分各自進行合併運算
* @param array 原始數組,即待排序數組
* @param tempArray 創建的臨時數組,array.length == tempArray.length
* @param left 左端下標
* @param right 右端下標
*/
private static void mergeSort(int[] array, int[] tempArray, int left, int right) {
// 待處理數組數量大於1
if(left < right){
int center = (left + right)/2;
mergeSort(array, tempArray, left, center);
mergeSort(array, tempArray, center+1, right);
merge(array, tempArray, left, center+1, right);
}
}
這是基本的合併算法,即將兩個已經排序的表,輸出放到第三個表中;最後再將排序好的臨時數組數據覆蓋到原始數組上,到此,歸併排序結束
/**
* 這是基本的合併算法,即將兩個已經排序的表,輸出放到第三個表中
* @param array 原始數組
* @param tempArray 創建的臨時數組
* @param leftPos 前半部分的起始下標 leftPosition
* @param rightPos 後半部分的起始下標 rightPosition
* @param rightEnd 後半部分的結束下標 rightEndPosition
*/
private static void merge(int[] array, int[] tempArray, int leftPos, int rightPos, int rightEnd) {
// 前部部分的結束下標
int leftEnd = rightPos - 1;
// 臨時數組的要添加元素的起始下標
int tempPos = leftPos;
// 臨時數組添加元素的總個數,即前半部分和後半部分元素的總數
int numElements = rightEnd - leftPos + 1;
// 前後兩部分都有元素沒有拷貝完畢
while (leftPos <= leftEnd && rightPos <= rightEnd) {
if(array[leftPos] <= array[rightPos]){
// 將前半部分小的數拷貝到臨時數組裏,隨後下標往後挪
tempArray[tempPos++] = array[leftPos++];
}else{
tempArray[tempPos++] = array[rightPos++];
}
}
// 當後半部分已經全部拷貝完畢,把前半部分剩餘的元素直接拷貝到臨時數組即可
while(leftPos <= leftEnd) {
tempArray[tempPos++] = array[leftPos++];
}
while(rightPos <= rightEnd) {
tempArray[tempPos++] = array[rightPos++];
}
// 將臨時數組裏的數據拷貝並覆蓋到原始數組相應位置即可
for(int i=0; i<numElements; i++,rightEnd--){
array[rightEnd] = tempArray[rightEnd];
}
}
四、總結
當然代碼編寫並不是固定的,肯定會有很多變形格式,其實我們研究的也是其歸併的思想和每種寫法的效率問題。如果想要查看更多算法基礎,去我的博客目錄裏查看吧,因爲關於每塊知識點的介紹,博客單節寫的比較零散,不容易查找。