歸併排序(二路歸併排序)是一個高效的排序算法,僅次於快速排序。
思想:將元素從中間一直往下切分,直到分解成1個元素(不能再分時)就開始將數組進行合併排序。
一直往下二分,不能再分時,再把所有的二分合並並且排序。
核心在於二分,還有就是合併時如何進行排序。
實現:二分使用遞歸,合併排序實際上是將兩個有序序列進行排序,解決這兩個問題,代碼就沒有任何問題了。
時間複雜度:O(nlogn)
空間複雜度:O(N) (使用了一個同等大小的臨時數組進行排序)
穩定性:穩定
假設數組爲{9,5,8,6,3,0,7,1}
下圖是我實現的Java代碼的執行過程:
(箭頭是java程序一直往下執行的順序。忽略箭頭,從上往下連起來,就是歸併排序的總體思想)
Java代碼實現
package org.example.sort;
import java.util.Arrays;
import java.util.Random;
/**
* 歸併排序
*
* @author Lee Xxn
* @date 2019-12-13
*/
public class MergeSort {
/**
* 歸併排序,傳入數組,返回排好序的數組
*
* @date 2019/12/13
* @param arr 待排序數組
* @return int[] 排好序的數組
*/
public static int [] sort(int [] arr){
/*歸併排序,首先是要把待排序的序列分段,一直分到不能分爲止
由於每個分段都需要被記錄並且在最後歸併時所有的分段需要分別排序,因此需要記錄每個分段的起始位置和結束位置,在這裏使用遞歸特別方便*/
//開始進行分段並且排序
splitAndMergeSort(arr,0,arr.length -1);
return arr;
}
/**
* 遞歸分段及歸併
*
* @date 2019/12/13
* @param arr 數組
* @param startIndex 分段開始索引
* @param endIndex 分段結束索引
*/
public static void splitAndMergeSort(int [] arr,int startIndex,int endIndex){
//使用遞歸,當分到不能再分的時候,結束遞歸
if(endIndex > startIndex){
//可以分段,則將數組從中間分成兩段
int mid = (startIndex + endIndex) / 2;
splitAndMergeSort(arr,startIndex,mid);
splitAndMergeSort(arr,mid +1 ,endIndex);
//不能再分的時候,將數組合並
mergeSort(arr,startIndex,mid,endIndex);
}
}
/**
* 合併並且排序
*
* @date 2019/12/13
* @param arr 數組
* @param leftStart 左段起始位置
* @param split 切分位置
* @param rightEnd 右段結束位置
*/
public static void mergeSort(int [] arr,int leftStart,int split,int rightEnd){
//合併排序,將左段數組和右段數組排序,插入到一個臨時數組,再把臨時數組拷貝到最終數組中,即把左右兩段排好序返回
//由於從1個數開始合併分段,並且返回的都是已經排好序的結果,所以最終是兩段排好序的序列進行排序
//因此排序只需要一個循環即可,左右兩端依次對比到末尾,對比結果插入到臨時數組,再把剩下的序列拷貝到臨時數組,就把兩個排好序的序列排序完成。
int [] temp = new int [arr.length];//使用臨時數組來存放排好序的序列,
int tempStart = leftStart;//記錄數組有意義的開始位置
int rightStart = split + 1;//右段的起始位置
int point = leftStart;//操作臨時數組的指針
//循環,左右兩端依次從頭進行比對(頭與頭對比,哪邊滿足條件,哪邊插入到臨時數組,然後往後移一位,只要有一端先移完,就可以終止循環,再比對已經沒有意義了,只需要拷貝移動即可)
while(leftStart <= split && rightStart <= rightEnd){
//升序
if(arr[leftStart]< arr[rightStart]){
temp[point++] = arr[leftStart++];
}else{
temp[point++] = arr[rightStart++];
}
}
//看看左右兩段哪邊沒有移完,如果沒有移完,說明還有剩下的序列,進行拷貝移動
while (leftStart <= split){
temp[point++] = arr[leftStart++];
}
while (rightStart <= rightEnd){
temp[point++] = arr[rightStart++];
}
//然後將有意義的數組拷貝到原數組中
for(int x = tempStart; x <= rightEnd;x++){
arr[x] = temp[x];
}
}
/**
* main 方法,用於驗證算法是否正確
*
*/
public static void main(String [] args){
int genArrNumber = 10;//生成10個測試的數組
int genArrSize = 10;//每個數組長度是10
for(int i = 0;i < genArrNumber;i++) {
int[] arr = new int[genArrSize];
for (int x = 0; x < genArrSize; x++) {
arr[x] = new Random().nextInt(100);
}
System.out.println("隨機生成的" + genArrSize + "個長度的數組" + Arrays.toString(arr));
System.out.println("歸併排序的結果:" + Arrays.toString(sort(arr)));
}
}
}