【Java】基础排序算法-归并排序
前言
归并算法是分治法的一个典型的应用,其核心思想就是将两个有序的数组归并为一个更大的有序数组
实现过程(自顶向下)
“归”:将数组不断拆分,直到每个子数组段只包含一个元素;
“并”:不断地将相邻两个子数组段合并成一个大的有序数组,直到整个数组有序
时间复杂度:O(N*logN)
空间复杂度:O(N)
稳定性:稳定
归并方法实现:
归并过程的实现最直接的方法就是新创建一个大小合适的数组,将两个输入的数组段的元素一个个从小到大放到这个数组里去。
关键代码:(“归并”过程)
//merger方法将两个有序子数组段arr[left...mid]和arr[mid+1...right]合并为一个更大的有序数组
public static void merge(int[] arr, int left, int mid, int right) {
int i = left;
int j = mid + 1;
//新建一个辅助数组暂存arr数组元素
int[] aux = new int[right - left + 1];
for (int k = 0; k < aux.length; k++) {
//子数组段索引从left开始,需从left位置开始拷贝
aux[k] = arr[k + left];
}
//遍历aux数组,此过程实现arr数组变有序
for (int k = left; k <= right; k++) {
//分别取i,j对应元素,较小值先存回数组arr
// i > mid说明左边有序数组段已经处理完毕,取右边元素
if (i > mid) {
arr[k] = aux[j - left];
j++;
} else if (j > right) {
// j > right 说明右边数组段处理完毕,取左边元素
arr[k] = aux[i - left];
i++;
} else if (aux[i - left] > aux[j - left]) {
//左边元素大于右边元素,取右边元素
arr[k] = aux[j - left];
j++;
} else {
//右边元素大于等于左边元素,取左边元素
arr[k] = aux[i - left];
i++;
}
}
}
主代码:(递归地将数组拆分,然后归并)
private static void mergeSortInternal(int[] arr, int left, int right) {
//优化一:当right-left <= 15 即每个子区间长度小于等于15最好,
// 此时用插入排序直接对每个子区间进行排序,会大大提高排序效率,减少拆分合并次数;
if (right - left <= 15) {
insertionShort(arr, left, right);
return;
}
// // 归而为一过程,不断将数组拆分为只含一个元素的过程;用上面优化操作更好
// // left == right说明数组已拆分为只含一个元素的数组
// if (left == right) {
// return;
// }
//将数组一分为二,mid表示左区间数组最后一个元素索引
int mid = left + ((right - left) >> 1);
//递归处理左区间
mergeSortInternal(arr, left, mid);
//递归处理右区间
mergeSortInternal(arr, mid + 1, right);
//优化二:
//如果左区间最后一个元素小于右区间第一个元素,说明此时两数组段已经组成一个有序大数组
//否则需要进行归并排序
if (arr[mid] > arr[mid + 1]) {
merge(arr, left, mid, right);
}
}
//优化一:小区间内进行插入排序效率会大大提升
private static void insertionShort(int[] arr, int left, int right) {
for (int i = left + 1; i <= right; i++) {
for (int j = i; j > left && arr[j] < arr[j - 1]; j--) {
swap(arr, j, j - 1);
}
}
}
优化一:实现上面的方法时并没有将数组拆分到只含有一个元素,而是拆分到每个子数组段大概包含15个元素左右,此时利用插入排序方法将子数组段直接排序,可以减少拆分合并次数,会大大提高排序效率;
优化二:在合并相邻子数组时,若左区间最后一个元素小于等于右区间第一个元素,说明此时两数组段已经组成了一个有序的大数组,此时不用执行归并过程。
归并排序非递归写法(自底向上):
自底向上排序时会多次遍历整个数组,根据子数组的大小进行两两合并,合并方法和上文相同,子数组初始大小值sz为1,每次加倍,最后一个子数组大小需要小于(数组长度为奇数时)或等于2*sz。
public static void mergeSortNonRecursion(int[] arr) {
for (int sz = 1; sz <= arr.length; sz += sz) {
for (int i = 0; i + sz < arr.length; i += 2 * sz) {
merge(arr, i, i + sz - 1, Math.min(i + 2 * sz - 1, arr.length - 1));
}
}
}
内容如有任何不妥之处,恳请指正