(1)算法描述
對於給定的線性序列,將當前序列不斷的進行分組,當每個分組的數據只有一個元素時,代表這個分組是有序的,那麼向上合併。每一層兩兩合併,合併的過程是,開闢一個新空間,使用兩個指針同時掃描兩個有序的分組,使得較小的元素或者較大的元素先進入新空間。在不斷的比較之後,如果一個分組的元素爲空,直接拷貝另一個沒有被用完的元素到新空間。
(2) 圖解算法
歸併排序的過程
合併函數的合併過程
(3) C/C++代碼實現
CustomSort.h
// 歸併排序
void MergeSort(int arr[], int number);
// 內部的歸併排序
void __MergeSort(int arr[], int left, int right);
// 內部合併
void __Merge(int arr[], int left, int mid, int right);
CustomSort.cpp
/************************************************************
- 功能描述:實現歸併排序
- int arr[]: 待排序的數組
- int number: 待排序數組中的元素
- 返回值:void
************************************************************/
void MergeSort(int arr[], int number) {
// 數組爲空或者有一個一下的元素直接返回
if(arr == NULL || number <= 1) {
return;
}
// 調用內部排序算法
__MergeSort(arr, 0, number-1);
}
/************************************************************
- 功能描述:實現歸併排序的內部排序
- int arr[]: 待排序的數組
- int left: 待排序數組的左邊界
- int right: 待排序數組的右邊界
- 返回值:void
************************************************************/
void __MergeSort(int arr[], int left, int right) {
// 如果 right <= left 沒有排序的必要性
if(right <= left) {
return;
}
// 計算中間值
// 沒有使用 (right + left) / 2 是爲了避免數據太大導致內存溢出
int mid = (right - left) / 2 + left;
// 分別排序兩個分組
__MergeSort(arr, left, mid);
__MergeSort(arr, mid+1, right);
// 排序完成後就合併兩個分組
__Merge(arr, left, mid, right);
}
/************************************************************
- 功能描述:實現歸併排序的分組合並
- int arr[]: 待排序的數組
- int left: 待合併數組的左邊界
- int right: 待合併數組的右邊界
- int mid: 待合併數組的中間值
- 返回值:void
************************************************************/
void __Merge(int arr[], int left, int mid, int right) {
int len = right - left + 1;
// 動態創建數組,因爲每個分組的大小都不一樣,使用完需要delete[]空間
int *temp = new int[len];
// 用於第一個分組的指針one
// 用於第二個分組的指針two
// 用於輔助數組的指針i
int one = left;
int two = mid + 1;
int i = 0;
// 判斷只要one和two都沒有越界就不斷的進行比較
while(one <= mid && two <= right) {
temp[i++] = arr[one] > arr[two] ? arr[one++] : arr[two++];
}
// 判斷哪個數組的指針還沒到頭就直接全都拷貝到temp數組
while(one <= mid) {
temp[i++] = arr[one++];
}
while(two <= right) {
temp[i++] = arr[two++];
}
// 往回拷貝數組,注意數組的位置
for(int j = 0; j < len; j++) {
arr[left + j] = temp[j];
}
delete[] temp;
}
(4) Java代碼實現
public class MergeSort {
// 歸併排序函數
public static void sort(int[] arr) {
// 如果數組爲null或者是數組中的元素小於2, 沒有排序的意義
if (arr == null || arr.length < 2) {
// 直接返回
return;
}
// 調用排序函數
mergeSort(arr, 0, arr.length - 1);
}
// 主要邏輯
public static void mergeSort(int[] arr, int l, int r) {
// 如果左邊的指針等於右邊指針, 也就是數組不能再分割
if (l == r) {
// 就要直接返回
return;
}
// 數組中間數值的指針位置
int mid = l + ((r - l) >> 1);
// 排序中間位置左邊的數組
mergeSort(arr, l, mid);
// 排序中間位置右邊的數組
mergeSort(arr, mid + 1, r);
// 合併函數
merge(arr, l, mid, r);
}
// 合併函數
public static void merge(int[] arr, int l, int m, int r) {
// 創建輔助數組
int[] help = new int[r - l + 1];
// 輔助數組的指針位置
int i = 0;
// 數組1的指針位置
int p1 = l;
// 數組2的指針位置
int p2 = m + 1;
// 判斷左邊和右邊的數組是否越界
while (p1 <= m && p2 <= r) {
// 如果都沒有越界, 向輔助數組添加數據,類似於外排的方式
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
// 如果第一個數組的指針還沒到頭, 就要拷貝數組
while (p1 <= m) {
help[i++] = arr[p1++];
}
// 如果第二個數組的指針還沒到頭, 就要拷貝數組
while (p2 <= r) {
help[i++] = arr[p2++];
}
// 拷貝排序後數組
for (i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
}
}
(5) 時間複雜度分析
當函數出現遞歸調用的時候,一個函數A調用了本身,假定是subA,那麼當前的函數A將會壓入系統的棧內,系統將會保存現場(包括函數執行到哪一行代碼,函數當前的調用狀態以及函數中變量的值),進行下一個函數的執行,經過一層層函數的調用,遇到一個返回條件,系統中的棧中保存的狀態將會一個一個的彈出,也就是函數的恢復現場,最後函數調用結束。
只要符合master公式的都可以使用一下方法計算時間複雜度:
master公式:T(N)=a*T(N/b)+O(Nd)
1) log(b, a) > d 複雜度是O(N log(b,a))
2) log(b, a) = d 複雜度是O(Nd * log(2, N))
3) log(b, a) < d 複雜度是O(Nd)
估計時間複雜度:左側部分的規模和右側部分的規模都是N/2,在整體外排的過程中總共劃過N個數,算式爲:T(N)=2T(N/2)+O(N)
,代入master公式,複雜度就是O(N*log2N)
,空間複雜度是O(N)
。