演算法-歸併排序
阿新 • • 發佈:2021-08-03
歸併排序
一、簡述
-
歸併排序是將兩個或兩個以上的有序表組合成一個新的有序表。
其基本思想是: 先將 N 個數據看成 N 個長度為 1 的表,將相鄰的表成對合並,得到長度為 2 的 N/2 個有序表,進一步將相鄰的合併,得到長度為 4 的 N/4 個有序表,以此類推,直到所有資料均合併成一個長度為 N 的有序表為止。每一次歸併過程稱做一趟。
- 那麼歸併排序就可以分解為,分解、歸併兩個子問題。
二、分解
分解方式分為“從上往下”和“從下往上”兩種方式。如下圖所示:
2.1 從上往下(遞迴)
分解:將當前空間一分為二,即求分裂點 mid=(left+right)/2;
求解:遞迴地對兩個子區間 a[left...mid]和 a[mid+1...right]進行歸併排序。遞迴的結束條件為子區間長度為 1(或left>=right)。
/** * 歸併排序 ,從上往下(遞迴) * @param arr 需要排序的陣列 * @param left 左節點 * @param right 右節點 */ public void mergeSort1(int[] arr, int left, int right) { if (left >= right) { return; } int mid = (left + right) / 2; // 左邊 mergeSort1(arr,left,mid); // 右邊 mergeSort1(arr, mid + 1, right); // 合併 mergeArr(arr, left, mid, right); }
2.2 從下往上
將待排序的數列分成若干個長度為 1 的子數列,然後將這些數列兩兩合併;得到若干個長度為 2 的有序數列,再將這些數列兩兩合併,得到長度為 4 的有序數列,直到合併成一個完整數列為止。這就得到了我們想要的排序結果
/** * 歸併排序 ,從下往上(非遞迴) * * @param arr 需要排序的陣列 */ public void mergeSort2(int[] arr) { if (null == arr || arr.length < 2) { return; } // 每組的個數 int mergeSize = 1; int N = arr.length; while (mergeSize < N) { int l = 0; while (l < N) { int m = l + mergeSize - 1; while (m >= N) { break; } int R = Math.min(m + mergeSize, N - 1); mergeArr(arr, l, m, R); l = R + 1; } // 越界處理,一般情況不會出現 if (mergeSize > N / 2) { break; } mergeSize <<= 1; } }
2.3歸併方法
將兩個子區間 arr[low...mid]和 arr[mid+1...high]歸併為一個有序的區間 arr[low...high]。
在歸併的過程中需要申請一個臨時陣列空間,將待排序的兩陣列順序的儲存在該臨時空間中。最後將有序的臨時空間覆蓋到原陣列中。此時陣列就變為區域性有序了。
/**
* 合併左右兩個區間陣列
* @param arr
* @param left
* @param mid
* @param right
*/
public void mergeArr(int[] arr, int left, int mid, int right) {
// 臨時的陣列
int[] temp = new int[right - left + 1];
int tempIndex = 0;
int p1 = left;
int p2 = mid + 1;
// p1 和p2 不越界的情況下
while (p1 <= mid && p2 <= right) {
temp[tempIndex++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
// 拷貝p1剩下部分
while (p1 <= mid) {
temp[tempIndex++] = arr[p1++];
}
// 拷貝p1剩下部分
while (p2 <= right) {
temp[tempIndex++] = arr[p2++];
}
// 將這部分排好序的部分刷入
for (int i = 0; i < temp.length; i++) {
arr[left + i] = temp[i];
}
}
三、效能分析
3.1 時間複雜度:
歸併排序的時間複雜度是O(nlgn)。
歸併排序的形式就是二叉樹,需要遍歷的次數就是二叉樹的深度,根據完全二叉樹的性質,其深度為lgn,則得出時間複雜度為O(n*lgn)。
public class MergeSortDemo1 {
/**
* 歸併排序 ,從上往下(遞迴)
* @param arr 需要排序的陣列
* @param left 左節點
* @param right 右節點
*/
public void mergeSort1(int[] arr, int left, int right) {
if (left >= right) {
return;
}
int mid = (left + right) / 2;
// 左邊
mergeSort1(arr,left,mid);
// 右邊
mergeSort1(arr, mid + 1, right);
// 合併
mergeArr(arr, left, mid, right);
}
/**
* 歸併排序 ,從下往上(非遞迴)
*
* @param arr 需要排序的陣列
*/
public void mergeSort2(int[] arr) {
if (null == arr || arr.length < 2) {
return;
}
// 每組的個數
int mergeSize = 1;
int N = arr.length;
while (mergeSize < N) {
int l = 0;
while (l < N) {
int m = l + mergeSize - 1;
while (m >= N) {
break;
}
int R = Math.min(m + mergeSize, N - 1);
mergeArr(arr, l, m, R);
l = R + 1;
}
// 越界處理,一般情況不會出現
if (mergeSize > N / 2) {
break;
}
mergeSize <<= 1;
}
}
/**
* 合併左右兩個區間陣列
* @param arr
* @param left
* @param mid
* @param right
*/
public void mergeArr(int[] arr, int left, int mid, int right) {
// 臨時的陣列
int[] temp = new int[right - left + 1];
int tempIndex = 0;
int p1 = left;
int p2 = mid + 1;
// p1 和p2 不越界的情況下
while (p1 <= mid && p2 <= right) {
temp[tempIndex++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
// 拷貝p1剩下部分
while (p1 <= mid) {
temp[tempIndex++] = arr[p1++];
}
// 拷貝p1剩下部分
while (p2 <= right) {
temp[tempIndex++] = arr[p2++];
}
// 將這部分排好序的部分刷入
for (int i = 0; i < temp.length; i++) {
arr[left + i] = temp[i];
}
}
private void sort1(int[] arr) {
mergeSort1(arr, 0, arr.length - 1);
}
private void sort2(int[] arr) {
mergeSort2(arr);
}
@Test
public void testMergeSort1() {
int[] arr = {11, 7, 2,11, 9, 8, 18};
sort1(arr);
for (int i : arr) {
System.out.print(i+" ");
}
}
@Test
public void testMergeSort2() {
int[] arr = {11, 7, 2,11, 9, 8, 18};
sort2(arr);
for (int i : arr) {
System.out.print(i+" ");
}
}
}