用Java 玩轉歸併排序
阿新 • • 發佈:2019-01-10
歸併排序
我們總是可以將一個數組一分為二,然後二分為四,直到每一組只有兩個元素,這可以理解為個遞迴的過程,然後將兩個元素進行排序,之後再將兩個元素為一組進行排序。直到所有的元素都排序完成。同樣我們來看下邊這個動圖。
歸併排序演算法是採用分治法的一個非常典型的應用,且各層分治遞迴可以同時進行。
歸併演算法的思想
歸併演算法其實可以分為遞迴法和迭代法(自底向上歸併),兩種實現對於最小集合的歸併操作思想是一樣的。區別在於如何劃分陣列,我們先介紹下演算法最基本的操作:
申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合併後的序列;
設定兩個指標,最初位置分別為兩個已經排序序列的起始位置;
比較兩個指標所指向的元素,選擇相對小的元素放入到合併空間,並移動指標到下一位置;
重複步驟 3 直到某一指標到達序列尾;
將另一序列剩下的所有元素直接複製到合併序列尾。
程式碼實現
public class Main {
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
private static void printArr(int[] arr) {
for (int anArr : arr) {
System. out.print(anArr + " ");
}
}
private static void mergeSort(int[] arr) {
if (arr == null)
return;
mergeSort(arr, 0, arr.length - 1);
}
private static void mergeSort(int[] arr, int start, int end) {
if (start >= end)
return;
// 找出中間索引
int mid = start + (end - start >> 1);
// 對左邊陣列進行遞迴
mergeSort(arr, start, mid);
// 對右邊陣列進行遞迴
mergeSort(arr, mid + 1, end);
// 合併
merge(arr, start, mid, end);
}
private static void merge(int[] arr, int start, int mid, int end) {
// 先建立一個臨時陣列,用於存放排序後的資料
int[] tmpArr = new int[arr.length];
int start1 = start, end1 = mid, start2 = mid + 1, end2 = end;
// 建立一個下標
int pos = start1;
// 快取左邊陣列的第一個元素的索引
int tmp = start1;
while (start1 <= end1 && start2 <= end2) {
// 從兩個陣列中取出最小的放入臨時陣列
if (arr[start1] <= arr[start2])
tmpArr[pos++] = arr[start1++];
else
tmpArr[pos++] = arr[start2++];
}
// 剩餘部分依次放入臨時陣列,實際上下面兩個 while 只會執行其中一個
while (start1 <= end1) {
tmpArr[pos++] = arr[start1++];
}
while (start2 <= end2) {
tmpArr[pos++] = arr[start2++];
}
// 將臨時陣列中的內容拷貝回原來的陣列中
while (tmp <= end) {
arr[tmp] = tmpArr[tmp++];
}
}
public static void main(String[] args) {
int[] arr = {6, 4, 2, 1, 8, 3, 7, 9, 5};
mergeSort(arr);
printArr(arr);
}
}
歸併排序演算法總的時間複雜度是 O(nlogn),而且這是歸併排序演算法中最好、最壞、平均的時間效能。
而由於在歸併排序過程中需要與原始記錄序列同樣數量的儲存空間存放歸併結果以及遞迴時壓入棧的資料佔用的空間:n + logn,所以空間複雜度為 O(n)。
總結
歸併排序雖然比較穩定,在時間上也是非常有效的,但是這種演算法很消耗空間,一般來說只有在外部排序才會採用這個方法,但在內部排序不會用這種方法,而是用快速排序。
轉自公眾號《nanchen》